/*-------------------------------------------------------------------------
 *
 * elog.c
 *	  错误记录和报告
 *
 * 由于可以生成日志消息的极高速度，我们需要注意获取任何可能被记录的信息的性能成本。此外，重要的是要记住，这段代码可能会在一个已中止的事务中被调用，在这种情况下，像syscache查找这样的操作是不安全的。
 *
 * 关于递归和错误处理期间错误的一些说明：
 *
 * 我们需要对递归错误场景保持稳健——例如，如果我们内存耗尽，那么报告这一事实是很重要的。对此有许多考虑因素。
 *
 * 首先，区分重入使用和实际递归。当错误或警告消息在计算错误消息的参数期间被发出时，有可能发生这种情况。在这种情况下，外部消息已调用errstart，一些字段值可能已被保存，但我们实际上并没有递归。我们通过提供一个（小）ErrorData记录栈来处理这一点。可以在不中断外部消息状态的情况下计算并发送内部消息。（如果内部消息实际上是错误的，那么这一点并不太有趣，因为控制不会返回到外部消息生成器……但如果内部消息只是调试或日志数据，那么这至关重要。）
 *
 * 其次，如果通过elog.c例程或它们调用的任何内容报告错误，则将发生实际递归。这种情况最有可能的场景是“内存耗尽”；而且处理起来也最麻烦，因为我们在试图报告此错误时可能也会耗尽内存！我们为这种情况提供的出路是，在尝试处理内部错误之前，将ErrorContext重置为空。由于ErrorContext保证至少有8K的空间（详见mcxt.c），因此我们应该能够成功处理“内存耗尽”消息。由于我们由于重置而丢失了先前的错误状态，因此我们将无法返回到处理原始错误，但我们本来也无法返回。（注意：对于内部消息的严重性低于ERROR的递归情况，不使用这一出路；在这种情况下，我们只是尝试处理它并正常返回。通常这将有效，但如果最终进入无限递归，我们将由于错误堆栈溢出而PANIC。）
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/utils/error/elog.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <fcntl.h>
#include <time.h>
#include <unistd.h>
#include <signal.h>
#include <ctype.h>
#ifdef HAVE_SYSLOG
#include <syslog.h>
#endif
#ifdef HAVE_EXECINFO_H
#include <execinfo.h>
#endif

#include "access/transam.h"
#include "access/xact.h"
#include "libpq/libpq.h"
#include "libpq/pqformat.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "postmaster/bgworker.h"
#include "postmaster/postmaster.h"
#include "postmaster/syslogger.h"
#include "storage/ipc.h"
#include "storage/proc.h"
#include "tcop/tcopprot.h"
#include "utils/guc.h"
#include "utils/memutils.h"
#include "utils/ps_status.h"


/* 在此模块中，通过 err_gettext() 访问 gettext() */
#undef _
#define _(x) fc_err_gettext(x)


/* 全局变量 */
ErrorContextCallback *error_context_stack = NULL;

sigjmp_buf *PG_exception_stack = NULL;

extern bool redirection_done;

/*
 * 拦截消息发送到服务器日志之前的钩子。
 * 请注意，钩子不会为被 log_min_messages 抑制的消息调用。
 * 还要注意，在预加载库中实现的日志钩子将会错过在库加载之前生成的任何日志消息。
 */
emit_log_hook_type emit_log_hook = NULL;

/* GUC 参数 */
int			Log_error_verbosity = PGERROR_VERBOSE;
char	   *Log_line_prefix = NULL; /* 附加日志行信息的格式 */
int			Log_destination = LOG_DESTINATION_STDERR;
char	   *Log_destination_string = NULL;
bool		syslog_sequence_numbers = true;
bool		syslog_split_messages = true;

#ifdef HAVE_SYSLOG

/*
 * 发送到 syslog() 的最大字符串长度。请注意，这不包括我们添加的
 * 序列号前缀，当然也不包括 syslog 本身添加的前缀。Solaris 和 sysklogd
 * 在 1024 字节处截断最终消息，因此该值为这些前缀留出了 124 字节。
 * （大多数其他 syslog 实现似乎有大约 2KB 的限制。）
 */
#ifndef PG_SYSLOG_LIMIT
#define PG_SYSLOG_LIMIT 900
#endif

static bool openlog_done = false;
static char *syslog_ident = NULL;
static int	syslog_facility = LOG_LOCAL0;

static void fc_write_syslog(int fc_level, const char *fc_line);
#endif

#ifdef WIN32
extern char *event_source;

static void write_eventlog(int level, const char *line, int len);
#endif

/* 我们为可重入情况提供了一个小的 ErrorData 记录栈 */
#define ERRORDATA_STACK_SIZE  5

static ErrorData errordata[ERRORDATA_STACK_SIZE];

static int	errordata_stack_depth = -1; /* 顶部活动帧的索引 */

static int	recursion_depth = 0;	/* 用于检测实际递归 */

/*
 * 保存的时间值和格式化时间戳的缓冲区，可能被 log_line_prefix 和 csv 日志使用。
 */
static struct timeval saved_timeval;
static bool saved_timeval_set = false;

#define FORMATTED_TS_LEN 128
static char formatted_start_time[FORMATTED_TS_LEN];
static char formatted_log_time[FORMATTED_TS_LEN];


/* 检查 errordata_stack_depth 是否合理的宏 */
#define CHECK_STACK_DEPTH() \
	do { \
		if (errordata_stack_depth < 0) \
		{ \
			errordata_stack_depth = -1; \
			ereport(ERROR, (errmsg_internal("errstart was not called"))); \
		} \
	} while (0)


static const char *fc_err_gettext(const char *fc_str) pg_attribute_format_arg(1);
static pg_noinline void fc_set_backtrace(ErrorData *fc_edata, int fc_num_skip);
static void fc_set_errdata_field(MemoryContextData *fc_cxt, char **fc_ptr, const char *fc_str);
static void fc_write_console(const char *fc_line, int fc_len);
static const char *fc_process_log_prefix_padding(const char *fc_p, int *fc_padding);
static void fc_log_line_prefix(StringInfo fc_buf, ErrorData *fc_edata);
static void fc_send_message_to_server_log(ErrorData *fc_edata);
static void fc_send_message_to_frontend(ErrorData *fc_edata);
static void fc_append_with_tabs(StringInfo fc_buf, const char *fc_str);


/*
 * is_log_level_output -- elevel 是否逻辑上 >= log_min_level?
 *
 * 我们将其用于应该考虑 LOG 的测试，以便对错误和致命错误进行排序。
 * 通常，这对于测试消息是否应该发送到 postmaster 日志是正确的，而简单的 >=
 * 测试对于测试消息是否应该发送到客户端是正确的。
 */
static inline bool fc_is_log_level_output(int fc_elevel, int fc_log_min_level)
{
	if (fc_elevel == LOG || fc_elevel == LOG_SERVER_ONLY)
	{
		if (fc_log_min_level == LOG || fc_log_min_level <= ERROR)
			return true;
	}
	else if (fc_elevel == WARNING_CLIENT_ONLY)
	{
		/* 无论 log_min_level 如何，均不会发送到日志 */
		return false;
	}
	else if (fc_log_min_level == LOG)
	{
		/* elevel != LOG */
		if (fc_elevel >= FATAL)
			return true;
	}
	/* LOG 也不是 */
	else if (fc_elevel >= fc_log_min_level)
		return true;

	return false;
}

/*
 * 策略设置子程序。这些相当简单，但将代码放在一个地方似乎明智。
 */

/*
 * should_output_to_server --- 给定 elevel 的消息是否应该发送到日志?
 */
static inline bool fc_should_output_to_server(int fc_elevel)
{
	return fc_is_log_level_output(fc_elevel, log_min_messages);
}

/*
 * should_output_to_client --- 给定 elevel 的消息是否应该发送到客户端?
 */
static inline bool fc_should_output_to_client(int fc_elevel)
{
	if (whereToSendOutput == DestRemote && fc_elevel != LOG_SERVER_ONLY)
	{
		/*
		 * client_min_messages 仅在我们完成身份验证握手后才会受到尊重。
		 * 这是出于安全考虑，并且许多客户端在身份验证过程中无法处理 NOTICE 消息。
		 */
		if (ClientAuthInProgress)
			return (fc_elevel >= ERROR);
		else
			return (fc_elevel >= client_min_messages || fc_elevel == INFO);
	}
	return false;
}


/*
 * message_level_is_interesting --- ereport/elog 会有什么动作吗?
 *
 * 如果具有此 elevel 的 ereport/elog 不会是无操作，则返回 true。
 * 这对于短路任何可能需要用于日志消息的昂贵准备工作是有用的。
 * 但是，将其放在裸露的 ereport/elog 调用之前是没有意义的。
 */
bool message_level_is_interesting(int fc_elevel)
{
	/*
	 * 确保与 errstart() 中的决策保持同步。
	 */
	if (fc_elevel >= ERROR ||
		fc_should_output_to_server(fc_elevel) ||
		fc_should_output_to_client(fc_elevel))
		return true;
	return false;
}


/*
 * in_error_recursion_trouble --- 我们是否面临无限错误递归的风险?
 *
 * 此函数的存在是为了在我们认为面临无限错误递归时提供对各种后备步骤的常见控制。
 * 请参阅调用者的详细说明。
 */
bool in_error_recursion_trouble(void)
{
	/* 如果递归超过一次，则拔掉插头 */
	return (recursion_depth > 2);
}

/*
 * 其中一个回退步骤是停止尝试本地化错误消息，因为有相当大的概率这正是导致递归的原因。
 */
static inline const char *
fc_err_gettext(const char *fc_str)
{
#ifdef ENABLE_NLS
	if (in_error_recursion_trouble())
		return fc_str;
	else
		return gettext(fc_str);
#else
	return fc_str;
#endif
}

/*
 * errstart_cold
 *		这是一个简单的 errstart 包装器，但被提示为“冷”。支持的编译器更有可能将包含此
 *		函数的分支代码移动到调用函数代码之外的区域。这可能导致更常执行的代码更紧凑，并
 *		适合于更少的缓存行。
 */
pg_attribute_cold bool
errstart_cold(int fc_elevel, const char *fc_domain)
{
	return errstart(fc_elevel, fc_domain);
}

/*
 * errstart --- 开始一个错误报告周期
 *
 * 创建并初始化错误栈条目。随后，将调用 errmsg() 以及可能其他例程来进一步填充栈条目。
 * 最后，将调用 errfinish() 来实际处理错误报告。
 *
 * 正常情况下返回 true。返回 false 以短路错误报告（如果它是警告或更低级别且不在任何地方
 * 报告）。
 */
bool errstart(int fc_elevel, const char *fc_domain)
{
	ErrorData  *fc_edata;
	bool		fc_output_to_server;
	bool		fc_output_to_client = false;
	int			fc_i;

	/*
	 * 检查一些我们希望将错误提升为更严重错误的情况。此逻辑不适用于非错误消息。
	 */
	if (fc_elevel >= ERROR)
	{
		/*
		 * 如果我们处于临界区，所有错误都变为 PANIC 错误。请参见 miscadmin.h。
		 */
		if (CritSectionCount > 0)
			fc_elevel = PANIC;

		/*
		 * 检查将 ERROR 视为 FATAL 的原因：
		 *
		 * 1. 我们没有处理此错误的处理程序（暗示我们在邮政处理程序或后台启动中）。
		 *
		 * 2. 设置了 ExitOnAnyError 模式切换（initdb 使用此模式）。
		 *
		 * 3. 错误发生在 proc_exit 开始运行之后。（proc_exit 负责确保这不会变成无限递归！）
		 */
		if (fc_elevel == ERROR)
		{
			if (PG_exception_stack == NULL ||
				ExitOnAnyError ||
				proc_exit_inprogress)
				fc_elevel = FATAL;
		}

		/*
		 * 如果错误级别为 ERROR 或更高，errfinish 将不会返回给调用者；因此，如果已有任何已
		 * 堆栈错误正在进行，将会丢失。这或多或少是可以的，只是我们不希望由于报告过程被低
		 * 级别错误中断而导致 FATAL 或 PANIC 错误降级。因此检查堆栈，并确保在必要时进行
		 * panic。
		 */
		for (fc_i = 0; fc_i <= errordata_stack_depth; fc_i++)
			fc_elevel = Max(fc_elevel, errordata[fc_i].elevel);
	}

	/*
	 * 现在决定是否需要处理此报告；如果是警告或更低级别且未启用日志记录，则直接返回
	 * false，而不启动任何错误日志记录机制。
	 */
	fc_output_to_server = fc_should_output_to_server(fc_elevel);
	fc_output_to_client = fc_should_output_to_client(fc_elevel);
	if (fc_elevel < ERROR && !fc_output_to_server && !fc_output_to_client)
		return false;

	/*
	 * 我们需要进行一些实际的工作。确保内存上下文初始化已经完成，否则我们无法做任何
	 * 有用的事情。
	 */
	if (ErrorContext == NULL)
	{
		/* 哎呀，发生严重崩溃；我们在这里能做的事情非常有限 */
		write_stderr("error occurred before error message processing is available\n");
		exit(2);
	}

	/*
	 * 好吧，启动一个栈条目来存储信息。
	 */

	if (recursion_depth++ > 0 && fc_elevel >= ERROR)
	{
		/*
		 * 哎呀，错误处理过程中发生错误。清除 ErrorContext，如文件顶部所讨论。我们将不会
		 * 返回到原始错误的报告者或处理程序，因此我们不需要它。
		 */
		MemoryContextReset(ErrorContext);

		/*
		 * 无限错误递归可能是由于上下文追溯例程中的某些问题造成的。
		 * 也放弃它们。我们还放弃了打印错误语句的尝试（如果很长，可能
		 * 自身就是递归失败的源头）。
		 */
		if (in_error_recursion_trouble())
		{
			error_context_stack = NULL;
			debug_query_string = NULL;
		}
	}
	if (++errordata_stack_depth >= ERRORDATA_STACK_SIZE)
	{
		/*
		 * 哎呀，栈不够大。我们将其视为PANIC条件，
		 * 因为这表明在错误恢复过程中发生了无限循环的错误。
		 */
		errordata_stack_depth = -1; /* 在栈上留出空间 */
		ereport(PANIC, (errmsg_internal("ERRORDATA_STACK_SIZE exceeded")));
	}

	/* 初始化此错误帧的数据 */
	fc_edata = &errordata[errordata_stack_depth];
	MemSet(fc_edata, 0, sizeof(ErrorData));
	fc_edata->elevel = fc_elevel;
	fc_edata->output_to_server = fc_output_to_server;
	fc_edata->output_to_client = fc_output_to_client;
	/* 默认文本域是后端的 */
	fc_edata->domain = fc_domain ? fc_domain : PG_TEXTDOMAIN("postgres");
	/* 以相同的方式初始化context_domain（参见set_errcontext_domain()） */
	fc_edata->context_domain = fc_edata->domain;
	/* 根据elevel选择默认的errcode */
	if (fc_elevel >= ERROR)
		fc_edata->sqlerrcode = ERRCODE_INTERNAL_ERROR;
	else if (fc_elevel >= WARNING)
		fc_edata->sqlerrcode = ERRCODE_WARNING;
	else
		fc_edata->sqlerrcode = ERRCODE_SUCCESSFUL_COMPLETION;
	/* errno在这里被保存，以便错误参数eval不会改变它 */
	fc_edata->saved_errno = errno;

	/*
	 * 所有与此错误状态级别相关的分配都应放入ErrorContext中
	 */
	fc_edata->assoc_context = ErrorContext;

	recursion_depth--;
	return true;
}

/*
 * 检查给定的funcname是否与backtrace_functions匹配；见
 * check_backtrace_functions。
 */
static bool fc_matches_backtrace_functions(const char *fc_funcname)
{
	char	   *fc_p;

	if (!backtrace_symbol_list || fc_funcname == NULL || fc_funcname[0] == '\0')
		return false;

	fc_p = backtrace_symbol_list;
	for (;;)
	{
		if (*fc_p == '\0')			/* backtrace_symbol_list的结束 */
			break;

		if (strcmp(fc_funcname, fc_p) == 0)
			return true;
		fc_p += strlen(fc_p) + 1;
	}

	return false;
}

/*
 * errfinish --- 结束错误报告周期
 *
 * 生成适当的错误报告并弹出错误栈。
 *
 * 如果传递给errstart()的elevel是ERROR或更严重，控制将不会
 * 返回给调用者。有关错误级别定义，请参见elog.h。
 */
void errfinish(const char *fc_filename, int fc_lineno, const char *fc_funcname)
{
	ErrorData  *fc_edata = &errordata[errordata_stack_depth];
	int			fc_elevel;
	MemoryContext fc_oldcontext;
	ErrorContextCallback *fc_econtext;

	recursion_depth++;
	CHECK_STACK_DEPTH();

	/* 将最后几位错误状态保存到栈条目中 */
	if (fc_filename)
	{
		const char *fc_slash;

		/* 仅保留基本名称，特别是对于vpath构建很有用 */
		fc_slash = strrchr(fc_filename, '/');
		if (fc_slash)
			fc_filename = fc_slash + 1;
		/* 一些Windows编译器在__FILE__字符串中使用反斜杠 */
		fc_slash = strrchr(fc_filename, '\\');
		if (fc_slash)
			fc_filename = fc_slash + 1;
	}

	fc_edata->filename = fc_filename;
	fc_edata->lineno = fc_lineno;
	fc_edata->funcname = fc_funcname;

	fc_elevel = fc_edata->elevel;

	/*
	 * 在ErrorContext中处理，我们希望有足够的保留空间
	 * 来报告错误。
	 */
	fc_oldcontext = MemoryContextSwitchTo(ErrorContext);

	if (!fc_edata->backtrace &&
		fc_edata->funcname &&
		backtrace_functions &&
		fc_matches_backtrace_functions(fc_edata->funcname))
		fc_set_backtrace(fc_edata, 2);

	/*
	 * 调用任何上下文回调函数。在回调
	 * 函数中发生的错误将被视为递归错误——这确保我们将
	 * 避免无限递归（参见errstart）。
	 */
	for (fc_econtext = error_context_stack;
		 fc_econtext != NULL;
		 fc_econtext = fc_econtext->previous)
		fc_econtext->callback(fc_econtext->arg);

	/*
	 * 如果是ERROR（不多也不少），我们将其传递给当前处理程序。
	 * 打印它和弹出栈是处理程序的责任。
	 */
	if (fc_elevel == ERROR)
	{
		/*
		 * 在longjmp之前我们进行一些最小的清理，以便处理程序可以
		 * 在一个合理的健康状态下执行。
		 *
		 * 重置InterruptHoldoffCount，以防我们在一个
		 * 中断保持部分中进行ereport。 （我们在这里假设没有处理程序
		 * 会自己处于保持部分内。如果必要，这样的处理程序
		 * 可以为自己保存和恢复InterruptHoldoffCount，但这
		 * 应该使大多数人的生活更容易。）
		 */
		InterruptHoldoffCount = 0;
		QueryCancelHoldoffCount = 0;

		CritSectionCount = 0;	/* 应该是不必要的，但... */

		/*
		 * 注意，我们将CurrentMemoryContext设置为ErrorContext。处理程序
		 * 应该很快将其重置为其他内容。
		 */

		recursion_depth--;
		PG_RE_THROW();
	}

	/* 将消息发送到正确的位置 */
	EmitErrorReport();

	/* 现在释放附加到栈条目的附属数据，并释放它 */
	if (fc_edata->message)
		pfree(fc_edata->message);
	if (fc_edata->detail)
		pfree(fc_edata->detail);
	if (fc_edata->detail_log)
		pfree(fc_edata->detail_log);
	if (fc_edata->hint)
		pfree(fc_edata->hint);
	if (fc_edata->context)
		pfree(fc_edata->context);
	if (fc_edata->backtrace)
		pfree(fc_edata->backtrace);
	if (fc_edata->schema_name)
		pfree(fc_edata->schema_name);
	if (fc_edata->table_name)
		pfree(fc_edata->table_name);
	if (fc_edata->column_name)
		pfree(fc_edata->column_name);
	if (fc_edata->datatype_name)
		pfree(fc_edata->datatype_name);
	if (fc_edata->constraint_name)
		pfree(fc_edata->constraint_name);
	if (fc_edata->internalquery)
		pfree(fc_edata->internalquery);

	errordata_stack_depth--;

	/* 退出错误处理上下文 */
	MemoryContextSwitchTo(fc_oldcontext);
	recursion_depth--;

	/*
	 * 根据elevel执行错误恢复操作。
	 */
	if (fc_elevel == FATAL)
	{
		/*
		 * 对于严重错误，我们让 proc_exit 进行清理并退出。
		 *
		 * 如果我们只是报告了启动失败，客户端在接收到它时将断开连接，
		 * 所以不要再发送任何信息给客户端。
		 */
		if (PG_exception_stack == NULL && whereToSendOutput == DestRemote)
			whereToSendOutput = DestNone;

		/*
		 * 刷新输出缓冲区只是为了提高我们看到错误消息的几率，
		 * 以防事情混乱到 proc_exit 崩溃。
		 * 你可能会想添加的任何其他代码应该放在 on_proc_exit 或 on_shmem_exit 回调中。
		 */
		fflush(stdout);
		fflush(stderr);

		/*
		 * 让累积统计系统知道。 仅当没有其他已知原因时，才将会话标记为
		 * 由于严重错误而终止。
		 */
		if (pgStatSessionEndCause == DISCONNECT_NORMAL)
			pgStatSessionEndCause = DISCONNECT_FATAL;

		/*
		 * 进行正常的进程退出清理，然后返回退出代码 1 来指示
		 * 严重终止。 Postmaster 可能会或可能不会认为这
		 * 值得恐慌，具体取决于哪个子进程返回它。
		 */
		proc_exit(1);
	}

	if (fc_elevel >= PANIC)
	{
		/*
		 * 严重崩溃时。 Postmaster 将观察 SIGABRT 进程退出
		 * 状态，并终止其他后台进程。
		 *
		 * XXX: 如果我们在 postmaster 中会怎样？ abort() 不会杀死我们的
		 * 子进程...
		 */
		fflush(stdout);
		fflush(stderr);
		abort();
	}

	/*
	 * 首先检查取消/终止中断 --- 这样用户可以
	 * 停止发出大量通知或警告消息的查询，即使它是在一个
	 * 否则未检查中断的循环中。
	 */
	CHECK_FOR_INTERRUPTS();
}


/*
 * errcode --- 将 SQLSTATE 错误代码添加到当前错误
 *
 * 该代码的表示形式应遵循 MAKE_SQLSTATE()。
 */
int errcode(int fc_sqlerrcode)
{
	ErrorData  *fc_edata = &errordata[errordata_stack_depth];

	/* 我们不再增加递归深度 */
	CHECK_STACK_DEPTH();

	fc_edata->sqlerrcode = fc_sqlerrcode;

	return 0;					/* 返回值无关紧要 */
}


/*
 * errcode_for_file_access --- 将 SQLSTATE 错误代码添加到当前错误
 *
 * SQLSTATE 代码是基于保存的 errno 值选择的。 我们假设
 * 失败的操作是某种类型的磁盘文件访问。
 *
 * 注意：使用此时，主要错误消息字符串通常应包含 %m。
 */
int errcode_for_file_access(void)
{
	ErrorData  *fc_edata = &errordata[errordata_stack_depth];

	/* 我们不再增加递归深度 */
	CHECK_STACK_DEPTH();

	switch (fc_edata->saved_errno)
	{
			/* 权限被拒绝的失败 */
		case EPERM:				/* 不是超级用户 */
		case EACCES:			/* 权限拒绝 */
#ifdef EROFS
		case EROFS:				/* 只读文件系统 */
#endif
			fc_edata->sqlerrcode = ERRCODE_INSUFFICIENT_PRIVILEGE;
			break;

			/* 找不到文件 */
		case ENOENT:			/* 没有这样的文件或目录 */
			fc_edata->sqlerrcode = ERRCODE_UNDEFINED_FILE;
			break;

			/* 重复文件 */
		case EEXIST:			/* 文件已存在 */
			fc_edata->sqlerrcode = ERRCODE_DUPLICATE_FILE;
			break;

			/* 错误的对象类型或状态 */
		case ENOTDIR:			/* 不是目录 */
		case EISDIR:			/* 是一个目录 */
#if defined(ENOTEMPTY) && (ENOTEMPTY != EEXIST) /* 在 AIX 上相同代码 */
		case ENOTEMPTY:			/* 目录不为空 */
#endif
			fc_edata->sqlerrcode = ERRCODE_WRONG_OBJECT_TYPE;
			break;

			/* 资源不足 */
		case ENOSPC:			/* 设备上没有剩余空间 */
			fc_edata->sqlerrcode = ERRCODE_DISK_FULL;
			break;

		case ENOMEM:			/* 内存不足 */
			fc_edata->sqlerrcode = ERRCODE_OUT_OF_MEMORY;
			break;

		case ENFILE:			/* 文件表溢出 */
		case EMFILE:			/* 打开的文件过多 */
			fc_edata->sqlerrcode = ERRCODE_INSUFFICIENT_RESOURCES;
			break;

			/* 硬件故障 */
		case EIO:				/* I/O 错误 */
			fc_edata->sqlerrcode = ERRCODE_IO_ERROR;
			break;

			/* 其他所有错误均归类为内部错误 */
		default:
			fc_edata->sqlerrcode = ERRCODE_INTERNAL_ERROR;
			break;
	}

	return 0;					/* 返回值无关紧要 */
}

/*
 * errcode_for_socket_access --- 将 SQLSTATE 错误代码添加到当前错误
 *
 * SQLSTATE 代码是基于保存的 errno 值选择的。 我们假设
 * 失败的操作是某种类型的套接字访问。
 *
 * 注意：使用此时，主要错误消息字符串通常应包含 %m。
 */
int errcode_for_socket_access(void)
{
	ErrorData  *fc_edata = &errordata[errordata_stack_depth];

	/* 我们不再增加递归深度 */
	CHECK_STACK_DEPTH();

	switch (fc_edata->saved_errno)
	{
			/* 连接丢失 */
		case ALL_CONNECTION_FAILURE_ERRNOS:
			fc_edata->sqlerrcode = ERRCODE_CONNECTION_FAILURE;
			break;

			/* 其他所有错误均归类为内部错误 */
		default:
			fc_edata->sqlerrcode = ERRCODE_INTERNAL_ERROR;
			break;
	}

	return 0;					/* 返回值无关紧要 */
}


/*
 * 该宏处理格式字符串及相关参数的扩展；
 * 这是errmsg()、errdetail()等的通用代码。必须在
 * 声明为“const char *fmt, ...”并设置了edata
 * 指针的例程中调用。消息被分配到edata->targetfield中，
 * 如果appendval为真，则附加到它。如果translateit为真，
 * 消息将被翻译。
 *
 * 注意：我们使用pstrdup复制缓冲区，而不是仅将其存储
 * 转移到edata字段，因为缓冲区可能大大超过
 * 实际所需的大小。
 */
#define EVALUATE_MESSAGE(domain, targetfield, appendval, translateit)	\
	{ \
		StringInfoData	fc_buf; \
		/* 国际化错误格式字符串 */ \
		if ((translateit) && !in_error_recursion_trouble()) \
			fc_fmt = dgettext((domain), fc_fmt);				  \
		initStringInfo(&fc_buf); \
		if ((appendval) && fc_edata->targetfield) { \
			appendStringInfoString(&fc_buf, fc_edata->targetfield); \
			appendStringInfoChar(&fc_buf, '\n'); \
		} \
		/* 生成实际输出---必须使用appendStringInfoVA */ \
		for (;;) \
		{ \
			va_list		fc_args; \
			int			fc_needed; \
			errno = fc_edata->saved_errno; \
			va_start(fc_args, fc_fmt); \
			fc_needed = appendStringInfoVA(&fc_buf, fc_fmt, fc_args); \
			va_end(fc_args); \
			if (fc_needed == 0) \
				break; \
			enlargeStringInfo(&fc_buf, fc_needed); \
		} \
		/* 将完成的消息保存到栈项中 */ \
		if (fc_edata->targetfield) \
			pfree(fc_edata->targetfield); \
		fc_edata->targetfield = pstrdup(fc_buf.data); \
		pfree(fc_buf.data); \
	}

/*
 * 同上，除了适用于复数形式的错误消息。调用例程
 * 必须声明为“const char *fmt_singular, const char *fmt_plural,
 * unsigned long n, ...”。假设始终需要翻译。
 */
#define EVALUATE_MESSAGE_PLURAL(domain, targetfield, appendval)  \
	{ \
		const char	   *fc_fmt; \
		StringInfoData	fc_buf; \
		/* 国际化错误格式字符串 */ \
		if (!in_error_recursion_trouble()) \
			fc_fmt = dngettext((domain), fc_fmt_singular, fc_fmt_plural, fc_n); \
		else \
			fc_fmt = (fc_n == 1 ? fc_fmt_singular : fc_fmt_plural); \
		initStringInfo(&fc_buf); \
		if ((appendval) && fc_edata->targetfield) { \
			appendStringInfoString(&fc_buf, fc_edata->targetfield); \
			appendStringInfoChar(&fc_buf, '\n'); \
		} \
		/* 生成实际输出---必须使用appendStringInfoVA */ \
		for (;;) \
		{ \
			va_list		fc_args; \
			int			fc_needed; \
			errno = fc_edata->saved_errno; \
			va_start(fc_args, fc_n); \
			fc_needed = appendStringInfoVA(&fc_buf, fc_fmt, fc_args); \
			va_end(fc_args); \
			if (fc_needed == 0) \
				break; \
			enlargeStringInfo(&fc_buf, fc_needed); \
		} \
		/* 将完成的消息保存到栈项中 */ \
		if (fc_edata->targetfield) \
			pfree(fc_edata->targetfield); \
		fc_edata->targetfield = pstrdup(fc_buf.data); \
		pfree(fc_buf.data); \
	}


/*
 * errmsg --- 将主要错误消息文本添加到当前错误中
 *
 * 除了printf识别的普通%-转义外，fmt中的“%m”
 * 被调用者的errno值的错误消息替换。
 *
 * 注意：fmt字符串末尾不需要换行，因为
 * ereport会为需要换行的输出方法提供一个换行符。
 */
int errmsg(const char *fc_fmt,...)
{
	ErrorData  *fc_edata = &errordata[errordata_stack_depth];
	MemoryContext fc_oldcontext;

	recursion_depth++;
	CHECK_STACK_DEPTH();
	fc_oldcontext = MemoryContextSwitchTo(fc_edata->assoc_context);

	fc_edata->message_id = fc_fmt;
	EVALUATE_MESSAGE(fc_edata->domain, message, false, true);

	MemoryContextSwitchTo(fc_oldcontext);
	recursion_depth--;
	return 0;					/* 返回值无关紧要 */
}

/*
 * 向包含的ereport()调用添加回溯。这是打算在
 * 调试过程中临时添加的。
 */
int errbacktrace(void)
{
	ErrorData  *fc_edata = &errordata[errordata_stack_depth];
	MemoryContext fc_oldcontext;

	recursion_depth++;
	CHECK_STACK_DEPTH();
	fc_oldcontext = MemoryContextSwitchTo(fc_edata->assoc_context);

	fc_set_backtrace(fc_edata, 1);

	MemoryContextSwitchTo(fc_oldcontext);
	recursion_depth--;

	return 0;
}

/*
 * 计算回溯数据并将其添加到提供的ErrorData中。num_skip
 * 指定要跳过多少个内部帧。使用此选项以避免在回溯中
 * 显示内部回溯支持函数。这需要该函数和相关函数
 * 不被内联。
 */
static void fc_set_backtrace(ErrorData *fc_edata, int fc_num_skip)
{
	StringInfoData fc_errtrace;

	initStringInfo(&fc_errtrace);

#ifdef HAVE_BACKTRACE_SYMBOLS
	{
		void	   *fc_buf[100];
		int			fc_nframes;
		char	  **fc_strfrms;

		fc_nframes = backtrace(fc_buf, lengthof(fc_buf));
		fc_strfrms = backtrace_symbols(fc_buf, fc_nframes);
		if (fc_strfrms == NULL)
			return;

		for (int fc_i = fc_num_skip; fc_i < fc_nframes; fc_i++)
			appendStringInfo(&fc_errtrace, "\n%s", fc_strfrms[fc_i]);
		free(fc_strfrms);
	}
#else
	appendStringInfoString(&errtrace,
						   "backtrace generation is not supported by this installation");
#endif

	fc_edata->backtrace = fc_errtrace.data;
}

/*
 * errmsg_internal --- 将主要错误消息文本添加到当前错误中
 *
 * 这与errmsg()完全相同，除了传递给errmsg_internal的
 * 字符串不被翻译，并且通常不包含在
 * 国际化消息字典中。这应当用于“不可能发生”的
 * 情况，可能不值得花费翻译精力。
 * 我们还在某些情况下使用此方法，其中我们*必须*
 * 不尝试翻译消息，因为翻译将失败并导致无限
 * 错误递归。
 */
int errmsg_internal(const char *fc_fmt,...)
{
	ErrorData  *fc_edata = &errordata[errordata_stack_depth];
	MemoryContext fc_oldcontext;

	recursion_depth++;
	CHECK_STACK_DEPTH();
	fc_oldcontext = MemoryContextSwitchTo(fc_edata->assoc_context);

	fc_edata->message_id = fc_fmt;
	EVALUATE_MESSAGE(fc_edata->domain, message, false, false);

	MemoryContextSwitchTo(fc_oldcontext);
	recursion_depth--;
	return 0;					/* 返回值无关紧要 */
}


/*
 * errmsg_plural --- 将主要错误消息文本添加到当前错误中，
 * 支持消息文本的复数化
 */
int errmsg_plural(const char *fc_fmt_singular, const char *fc_fmt_plural,
			  unsigned long fc_n,...)
{
	ErrorData  *fc_edata = &errordata[errordata_stack_depth];
	MemoryContext fc_oldcontext;

	recursion_depth++;
	CHECK_STACK_DEPTH();
	fc_oldcontext = MemoryContextSwitchTo(fc_edata->assoc_context);

	fc_edata->message_id = fc_fmt_singular;
	EVALUATE_MESSAGE_PLURAL(fc_edata->domain, message, false);

	MemoryContextSwitchTo(fc_oldcontext);
	recursion_depth--;
	return 0;					/* 返回值无关紧要 */
}


/*
 * errdetail --- 将详细错误消息文本添加到当前错误中
 */
int errdetail(const char *fc_fmt,...)
{
	ErrorData  *fc_edata = &errordata[errordata_stack_depth];
	MemoryContext fc_oldcontext;

	recursion_depth++;
	CHECK_STACK_DEPTH();
	fc_oldcontext = MemoryContextSwitchTo(fc_edata->assoc_context);

	EVALUATE_MESSAGE(fc_edata->domain, detail, false, true);

	MemoryContextSwitchTo(fc_oldcontext);
	recursion_depth--;
	return 0;					/* 返回值无关紧要 */
}


/*
 * errdetail_internal --- 将详细错误消息文本添加到当前错误
 *
 * 这与 errdetail() 完全相同，除了传递给
 * errdetail_internal 的字符串没有被翻译，并且通常不包含在
 * 国际化消息字典中。这应该用于因为某种原因而似乎不值得翻译的详细
 * 消息（通常是因为它们似乎对普通用户没有用处）。
 */
int errdetail_internal(const char *fc_fmt,...)
{
	ErrorData  *fc_edata = &errordata[errordata_stack_depth];
	MemoryContext fc_oldcontext;

	recursion_depth++;
	CHECK_STACK_DEPTH();
	fc_oldcontext = MemoryContextSwitchTo(fc_edata->assoc_context);

	EVALUATE_MESSAGE(fc_edata->domain, detail, false, false);

	MemoryContextSwitchTo(fc_oldcontext);
	recursion_depth--;
	return 0;					/* 返回值无关紧要 */
}


/*
 * errdetail_log --- 将 detail_log 错误消息文本添加到当前错误
 */
int errdetail_log(const char *fc_fmt,...)
{
	ErrorData  *fc_edata = &errordata[errordata_stack_depth];
	MemoryContext fc_oldcontext;

	recursion_depth++;
	CHECK_STACK_DEPTH();
	fc_oldcontext = MemoryContextSwitchTo(fc_edata->assoc_context);

	EVALUATE_MESSAGE(fc_edata->domain, detail_log, false, true);

	MemoryContextSwitchTo(fc_oldcontext);
	recursion_depth--;
	return 0;					/* 返回值无关紧要 */
}

/*
 * errdetail_log_plural --- 将 detail_log 错误消息文本添加到当前错误
 * 并支持消息文本的复数形式
 */
int errdetail_log_plural(const char *fc_fmt_singular, const char *fc_fmt_plural,
					 unsigned long fc_n,...)
{
	ErrorData  *fc_edata = &errordata[errordata_stack_depth];
	MemoryContext fc_oldcontext;

	recursion_depth++;
	CHECK_STACK_DEPTH();
	fc_oldcontext = MemoryContextSwitchTo(fc_edata->assoc_context);

	EVALUATE_MESSAGE_PLURAL(fc_edata->domain, detail_log, false);

	MemoryContextSwitchTo(fc_oldcontext);
	recursion_depth--;
	return 0;					/* 返回值无关紧要 */
}


/*
 * errdetail_plural --- 将详细错误消息文本添加到当前错误，
 * 并支持消息文本的复数形式
 */
int errdetail_plural(const char *fc_fmt_singular, const char *fc_fmt_plural,
				 unsigned long fc_n,...)
{
	ErrorData  *fc_edata = &errordata[errordata_stack_depth];
	MemoryContext fc_oldcontext;

	recursion_depth++;
	CHECK_STACK_DEPTH();
	fc_oldcontext = MemoryContextSwitchTo(fc_edata->assoc_context);

	EVALUATE_MESSAGE_PLURAL(fc_edata->domain, detail, false);

	MemoryContextSwitchTo(fc_oldcontext);
	recursion_depth--;
	return 0;					/* 返回值无关紧要 */
}


/*
 * errhint --- 将提示错误消息文本添加到当前错误
 */
int errhint(const char *fc_fmt,...)
{
	ErrorData  *fc_edata = &errordata[errordata_stack_depth];
	MemoryContext fc_oldcontext;

	recursion_depth++;
	CHECK_STACK_DEPTH();
	fc_oldcontext = MemoryContextSwitchTo(fc_edata->assoc_context);

	EVALUATE_MESSAGE(fc_edata->domain, hint, false, true);

	MemoryContextSwitchTo(fc_oldcontext);
	recursion_depth--;
	return 0;					/* 返回值无关紧要 */
}


/*
 * errhint_plural --- 将提示错误消息文本添加到当前错误，
 * 并支持消息文本的复数形式
 */
int errhint_plural(const char *fc_fmt_singular, const char *fc_fmt_plural,
			   unsigned long fc_n,...)
{
	ErrorData  *fc_edata = &errordata[errordata_stack_depth];
	MemoryContext fc_oldcontext;

	recursion_depth++;
	CHECK_STACK_DEPTH();
	fc_oldcontext = MemoryContextSwitchTo(fc_edata->assoc_context);

	EVALUATE_MESSAGE_PLURAL(fc_edata->domain, hint, false);

	MemoryContextSwitchTo(fc_oldcontext);
	recursion_depth--;
	return 0;					/* 返回值无关紧要 */
}


/*
 * errcontext_msg --- 将上下文错误消息文本添加到当前错误
 *
 * 与其他情况不同，允许多次调用以建立上下文信息栈。
 * 我们假设早期调用表示更紧密嵌套的状态。
 */
int errcontext_msg(const char *fc_fmt,...)
{
	ErrorData  *fc_edata = &errordata[errordata_stack_depth];
	MemoryContext fc_oldcontext;

	recursion_depth++;
	CHECK_STACK_DEPTH();
	fc_oldcontext = MemoryContextSwitchTo(fc_edata->assoc_context);

	EVALUATE_MESSAGE(fc_edata->context_domain, context, true, true);

	MemoryContextSwitchTo(fc_oldcontext);
	recursion_depth--;
	return 0;					/* 返回值无关紧要 */
}

/*
 * set_errcontext_domain --- 设置 errcontext() 使用的消息域
 *
 * errcontext_msg() 可以从与原始
 * ereport() 不同的模块中调用，因此我们不能使用在 errstart() 中传递的
 * 消息域来进行翻译。相反，每个 errcontext_msg() 调用应该在
 * set_errcontext_domain() 调用之前，以指定领域。这通常由
 * errcontext() 宏透明地完成。
 */
int set_errcontext_domain(const char *fc_domain)
{
	ErrorData  *fc_edata = &errordata[errordata_stack_depth];

	/* 我们不再增加递归深度 */
	CHECK_STACK_DEPTH();

	/* 默认文本域是后端的 */
	fc_edata->context_domain = fc_domain ? fc_domain : PG_TEXTDOMAIN("postgres");

	return 0;					/* 返回值无关紧要 */
}


/*
 * errhidestmt --- 可选地抑制日志条目的 STATEMENT: 字段
 *
 * 如果消息文本已包含该语句，则应调用此方法。
 */
int errhidestmt(bool fc_hide_stmt)
{
	ErrorData  *fc_edata = &errordata[errordata_stack_depth];

	/* 我们不再增加递归深度 */
	CHECK_STACK_DEPTH();

	fc_edata->hide_stmt = fc_hide_stmt;

	return 0;					/* 返回值无关紧要 */
}

/*
 * errhidecontext --- 可选地抑制日志条目的 CONTEXT: 字段
 *
 * 这应仅用于详细调试消息，其中重复包含上下文会使日志体积过大。
 */
int errhidecontext(bool fc_hide_ctx)
{
	ErrorData  *fc_edata = &errordata[errordata_stack_depth];

	/* 我们不再增加递归深度 */
	CHECK_STACK_DEPTH();

	fc_edata->hide_ctx = fc_hide_ctx;

	return 0;					/* 返回值无关紧要 */
}

/*
 * errposition --- 将光标位置添加到当前错误
 */
int errposition(int fc_cursorpos)
{
	ErrorData  *fc_edata = &errordata[errordata_stack_depth];

	/* 我们不再增加递归深度 */
	CHECK_STACK_DEPTH();

	fc_edata->cursorpos = fc_cursorpos;

	return 0;					/* 返回值无关紧要 */
}

/*
 * internalerrposition --- 将内部游标位置添加到当前错误中
 */
int internalerrposition(int fc_cursorpos)
{
	ErrorData  *fc_edata = &errordata[errordata_stack_depth];

	/* 我们不再增加递归深度 */
	CHECK_STACK_DEPTH();

	fc_edata->internalpos = fc_cursorpos;

	return 0;					/* 返回值无关紧要 */
}

/*
 * internalerrquery --- 将内部查询文本添加到当前错误中
 *
 * 也可以传递 NULL 以删除内部查询文本条目。此情况
 * 旨在用于在错误报告的布局上进行编辑的错误回调子例程。
 */
int internalerrquery(const char *fc_query)
{
	ErrorData  *fc_edata = &errordata[errordata_stack_depth];

	/* 我们不再增加递归深度 */
	CHECK_STACK_DEPTH();

	if (fc_edata->internalquery)
	{
		pfree(fc_edata->internalquery);
		fc_edata->internalquery = NULL;
	}

	if (fc_query)
		fc_edata->internalquery = MemoryContextStrdup(fc_edata->assoc_context, fc_query);

	return 0;					/* 返回值无关紧要 */
}

/*
 * err_generic_string -- 用于设置由 PG_DIAG_xxx 代码标识的单独 ErrorData 字符串字段。
 *
 * 这故意只支持不使用本地化字符串的字段，
 * 因此没有翻译的考虑。
 *
 * 大多数潜在的调用者不应直接使用此方法，而应更倾向于
 * 更高级的抽象，例如 errtablecol() （参见 relcache.c）。
 */
int err_generic_string(int fc_field, const char *fc_str)
{
	ErrorData  *fc_edata = &errordata[errordata_stack_depth];

	/* 我们不再增加递归深度 */
	CHECK_STACK_DEPTH();

	switch (fc_field)
	{
		case PG_DIAG_SCHEMA_NAME:
			fc_set_errdata_field(fc_edata->assoc_context, &fc_edata->schema_name, fc_str);
			break;
		case PG_DIAG_TABLE_NAME:
			fc_set_errdata_field(fc_edata->assoc_context, &fc_edata->table_name, fc_str);
			break;
		case PG_DIAG_COLUMN_NAME:
			fc_set_errdata_field(fc_edata->assoc_context, &fc_edata->column_name, fc_str);
			break;
		case PG_DIAG_DATATYPE_NAME:
			fc_set_errdata_field(fc_edata->assoc_context, &fc_edata->datatype_name, fc_str);
			break;
		case PG_DIAG_CONSTRAINT_NAME:
			fc_set_errdata_field(fc_edata->assoc_context, &fc_edata->constraint_name, fc_str);
			break;
		default:
			elog(ERROR, "unsupported ErrorData field id: %d", fc_field);
			break;
	}

	return 0;					/* 返回值无关紧要 */
}

/*
 * set_errdata_field --- 设置 ErrorData 字符串字段
 */
static void fc_set_errdata_field(MemoryContextData *fc_cxt, char **fc_ptr, const char *fc_str)
{
	Assert(*fc_ptr == NULL);
	*fc_ptr = MemoryContextStrdup(fc_cxt, fc_str);
}

/*
 * geterrcode --- 返回当前设置的 SQLSTATE 错误代码
 *
 * 这仅用于错误回调子例程，因为在 elog.c 之外
 * 没有其他地方这个概念是有意义的。
 */
int geterrcode(void)
{
	ErrorData  *fc_edata = &errordata[errordata_stack_depth];

	/* 我们不再增加递归深度 */
	CHECK_STACK_DEPTH();

	return fc_edata->sqlerrcode;
}

/*
 * geterrposition --- 返回当前设置的错误位置（如果没有则为 0）
 *
 * 这仅用于错误回调子例程，因为在 elog.c 之外
 * 没有其他地方这个概念是有意义的。
 */
int geterrposition(void)
{
	ErrorData  *fc_edata = &errordata[errordata_stack_depth];

	/* 我们不再增加递归深度 */
	CHECK_STACK_DEPTH();

	return fc_edata->cursorpos;
}

/*
 * getinternalerrposition --- 同样适用于内部错误位置
 *
 * 这仅用于错误回调子例程，因为在 elog.c 之外
 * 没有其他地方这个概念是有意义的。
 */
int getinternalerrposition(void)
{
	ErrorData  *fc_edata = &errordata[errordata_stack_depth];

	/* 我们不再增加递归深度 */
	CHECK_STACK_DEPTH();

	return fc_edata->internalpos;
}


/*
 * 函数允许单独构造错误消息字符串，而不依赖于
 * ereport() 调用本身。
 *
 * 预期的调用约定是
 *
 *	pre_format_elog_string(errno, domain), var = format_elog_string(format,...)
 *
 * 这可以隐藏在宏后面，例如 GUC_check_errdetail()。我们
 * 假设在 format_elog_string() 的参数中调用的任何函数
 * 不能导致这些函数的重入使用 --- 否则可能会使用错误
 * 的文本域，或者错误的 errno 替代 %m。对于
 * 当前与 GUC 检查钩子的使用情况这是可以的，但未来可能需要
 * 进一步的努力。
 *
 * format_elog_string() 的结果存储在 ErrorContext 中，
 * 因此在调用 FlushErrorState() 之前将会保留该结果。
 */
static int	save_format_errnumber;
static const char *save_format_domain;

void pre_format_elog_string(int fc_errnumber, const char *fc_domain)
{
	/* 在评估参数函数可能更改 errno 之前保存 errno */
	save_format_errnumber = fc_errnumber;
	/* 保存调用者的文本域 */
	save_format_domain = fc_domain;
}

char * format_elog_string(const char *fc_fmt,...)
{
	ErrorData	fc_errdata;
	ErrorData  *fc_edata;
	MemoryContext fc_oldcontext;

	/* 初始化一个几乎是虚假的错误框架 */
	fc_edata = &fc_errdata;
	MemSet(fc_edata, 0, sizeof(ErrorData));
	/* 默认文本域是后端的 */
	fc_edata->domain = save_format_domain ? save_format_domain : PG_TEXTDOMAIN("postgres");
	/* 设置用于解释 %m 的 errno */
	fc_edata->saved_errno = save_format_errnumber;

	fc_oldcontext = MemoryContextSwitchTo(ErrorContext);

	fc_edata->message_id = fc_fmt;
	EVALUATE_MESSAGE(fc_edata->domain, message, false, true);

	MemoryContextSwitchTo(fc_oldcontext);

	return fc_edata->message;
}


/*
 * 实际的堆栈顶部错误消息输出
 *
 * 在 ereport(ERROR) 的情况下，这从 PostgresMain 被调用（或者根本不被调用，
 * 如果错误被某个地方捕获）。对于所有其他严重级别，这由 errfinish 调用。
 */
void EmitErrorReport(void)
{
	ErrorData  *fc_edata = &errordata[errordata_stack_depth];
	MemoryContext fc_oldcontext;

	recursion_depth++;
	CHECK_STACK_DEPTH();
	fc_oldcontext = MemoryContextSwitchTo(fc_edata->assoc_context);

	/*
	 * 在发送消息到日志之前调用钩子。钩子函数被允许
	 * 关闭 edata->output_to_server，因此我们必须在之后重新检查这一点。
	 * 对 edata 内容的任何其他更改都不被认为是
	 * 支持的。
	 *
	 * 注意：钩子只能关闭 output_to_server，而不能打开它的原因是，这样做是不可靠的：
	 * 如果 errstart() 认为该消息不有趣，我们将根本不会来到这里。
	 * 一个能够在这个方向上做出决策的钩子必须钩入 errstart()，
	 * 在那里可用的信息会少得多。emit_log_hook
	 * 旨在用于自定义日志过滤和自定义日志消息传输
	 * 机制。
	 *
	 * 日志钩子可以访问经过翻译和原始英文的
	 * 错误消息文本，这些文本被传递以允许它作为
	 * 消息标识符使用。注意，原始文本在
	 * detail、detail_log、hint 和 context 文本元素中不可用。
	 */
	if (fc_edata->output_to_server && emit_log_hook)
		(*emit_log_hook) (fc_edata);

	/* 发送到服务器日志，如果启用 */
	if (fc_edata->output_to_server)
		fc_send_message_to_server_log(fc_edata);

	/* 发送到客户端，如果启用 */
	if (fc_edata->output_to_client)
		fc_send_message_to_frontend(fc_edata);

	MemoryContextSwitchTo(fc_oldcontext);
	recursion_depth--;
}

/*
 * CopyErrorData --- 获取顶部错误堆栈条目的副本
 *
 * 这仅用于错误处理程序代码。数据被复制到当前内存上下文中，
 * 因此调用者应始终首先切换离开 ErrorContext；否则它将在 FlushErrorState 完成时丢失。
 */
ErrorData *
CopyErrorData(void)
{
	ErrorData  *fc_edata = &errordata[errordata_stack_depth];
	ErrorData  *fc_newedata;

	/*
	 * 我们不增加 recursion_depth，因为这里的内存不足并不
	 * 表示错误子系统内部存在问题。
	 */
	CHECK_STACK_DEPTH();

	Assert(CurrentMemoryContext != ErrorContext);

	/* 复制结构本身 */
	fc_newedata = (ErrorData *) palloc(sizeof(ErrorData));
	memcpy(fc_newedata, fc_edata, sizeof(ErrorData));

	/*
	 * 复制单独分配的字符串。注意，我们甚至复制
	 * 理论上是恒定的字符串，例如文件名。这是因为那些
	 * 可能指向 JIT 创建的代码段，可能会在
	 * 事务清理时被卸载。在某些情况下，我们需要复制的 ErrorData 
	 * 跨越事务边界生存，因此我们最好也复制这些字符串。
	 */
	if (fc_newedata->filename)
		fc_newedata->filename = pstrdup(fc_newedata->filename);
	if (fc_newedata->funcname)
		fc_newedata->funcname = pstrdup(fc_newedata->funcname);
	if (fc_newedata->domain)
		fc_newedata->domain = pstrdup(fc_newedata->domain);
	if (fc_newedata->context_domain)
		fc_newedata->context_domain = pstrdup(fc_newedata->context_domain);
	if (fc_newedata->message)
		fc_newedata->message = pstrdup(fc_newedata->message);
	if (fc_newedata->detail)
		fc_newedata->detail = pstrdup(fc_newedata->detail);
	if (fc_newedata->detail_log)
		fc_newedata->detail_log = pstrdup(fc_newedata->detail_log);
	if (fc_newedata->hint)
		fc_newedata->hint = pstrdup(fc_newedata->hint);
	if (fc_newedata->context)
		fc_newedata->context = pstrdup(fc_newedata->context);
	if (fc_newedata->backtrace)
		fc_newedata->backtrace = pstrdup(fc_newedata->backtrace);
	if (fc_newedata->message_id)
		fc_newedata->message_id = pstrdup(fc_newedata->message_id);
	if (fc_newedata->schema_name)
		fc_newedata->schema_name = pstrdup(fc_newedata->schema_name);
	if (fc_newedata->table_name)
		fc_newedata->table_name = pstrdup(fc_newedata->table_name);
	if (fc_newedata->column_name)
		fc_newedata->column_name = pstrdup(fc_newedata->column_name);
	if (fc_newedata->datatype_name)
		fc_newedata->datatype_name = pstrdup(fc_newedata->datatype_name);
	if (fc_newedata->constraint_name)
		fc_newedata->constraint_name = pstrdup(fc_newedata->constraint_name);
	if (fc_newedata->internalquery)
		fc_newedata->internalquery = pstrdup(fc_newedata->internalquery);

	/* 使用调用上下文进行字符串分配 */
	fc_newedata->assoc_context = CurrentMemoryContext;

	return fc_newedata;
}

/*
 * FreeErrorData --- 释放由 CopyErrorData 返回的结构。
 *
 * 错误处理程序应优先使用此方法，而不是假设它们了解所有
 * 单独分配的字段。
 */
void FreeErrorData(ErrorData *fc_edata)
{
	if (fc_edata->message)
		pfree(fc_edata->message);
	if (fc_edata->detail)
		pfree(fc_edata->detail);
	if (fc_edata->detail_log)
		pfree(fc_edata->detail_log);
	if (fc_edata->hint)
		pfree(fc_edata->hint);
	if (fc_edata->context)
		pfree(fc_edata->context);
	if (fc_edata->backtrace)
		pfree(fc_edata->backtrace);
	if (fc_edata->schema_name)
		pfree(fc_edata->schema_name);
	if (fc_edata->table_name)
		pfree(fc_edata->table_name);
	if (fc_edata->column_name)
		pfree(fc_edata->column_name);
	if (fc_edata->datatype_name)
		pfree(fc_edata->datatype_name);
	if (fc_edata->constraint_name)
		pfree(fc_edata->constraint_name);
	if (fc_edata->internalquery)
		pfree(fc_edata->internalquery);
	pfree(fc_edata);
}

/*
 * FlushErrorState --- 在错误恢复后刷新错误状态
 *
 * 这应该由错误处理程序在完成处理
 * 错误后调用；或者在完成 CopyErrorData 后尽快调用，
 * 如果它打算做可能会引发另一个错误的操作。直到你执行了这个，
 * 你都不算“脱离”错误子系统。
 */
void FlushErrorState(void)
{
	/*
	 * 重置堆栈为空。它唯一的多层情况是我们处理了一个中断了另一个消息构造的错误。我们假设控制已逃离该消息的构造，并且不会再返回。
	 */
	errordata_stack_depth = -1;
	recursion_depth = 0;
	/* 删除 ErrorContext 中的所有数据 */
	MemoryContextResetAndDeleteChildren(ErrorContext);
}

/*
 * ThrowErrorData --- 报告由 ErrorData 结构描述的错误
 *
 * 这有点像 ReThrowError，但它允许除了 ERROR 以外的级别，并且像 output_to_server 这样的布尔标志是通过默认规则计算的，而不是从给定的 ErrorData 中复制。主要用于重新报告最初由后台工作进程报告的错误，然后传播（有或没有修改）给负责它们的后端。
 */
void ThrowErrorData(ErrorData *fc_edata)
{
	ErrorData  *fc_newedata;
	MemoryContext fc_oldcontext;

	if (!errstart(fc_edata->elevel, fc_edata->domain))
		return;					/* 错误完全不报告 */

	fc_newedata = &errordata[errordata_stack_depth];
	recursion_depth++;
	fc_oldcontext = MemoryContextSwitchTo(fc_newedata->assoc_context);

	/* 将提供的字段复制到错误堆栈条目中。 */
	if (fc_edata->sqlerrcode != 0)
		fc_newedata->sqlerrcode = fc_edata->sqlerrcode;
	if (fc_edata->message)
		fc_newedata->message = pstrdup(fc_edata->message);
	if (fc_edata->detail)
		fc_newedata->detail = pstrdup(fc_edata->detail);
	if (fc_edata->detail_log)
		fc_newedata->detail_log = pstrdup(fc_edata->detail_log);
	if (fc_edata->hint)
		fc_newedata->hint = pstrdup(fc_edata->hint);
	if (fc_edata->context)
		fc_newedata->context = pstrdup(fc_edata->context);
	if (fc_edata->backtrace)
		fc_newedata->backtrace = pstrdup(fc_edata->backtrace);
	/* 假设 message_id 不可用 */
	if (fc_edata->schema_name)
		fc_newedata->schema_name = pstrdup(fc_edata->schema_name);
	if (fc_edata->table_name)
		fc_newedata->table_name = pstrdup(fc_edata->table_name);
	if (fc_edata->column_name)
		fc_newedata->column_name = pstrdup(fc_edata->column_name);
	if (fc_edata->datatype_name)
		fc_newedata->datatype_name = pstrdup(fc_edata->datatype_name);
	if (fc_edata->constraint_name)
		fc_newedata->constraint_name = pstrdup(fc_edata->constraint_name);
	fc_newedata->cursorpos = fc_edata->cursorpos;
	fc_newedata->internalpos = fc_edata->internalpos;
	if (fc_edata->internalquery)
		fc_newedata->internalquery = pstrdup(fc_edata->internalquery);

	MemoryContextSwitchTo(fc_oldcontext);
	recursion_depth--;

	/* 处理错误。 */
	errfinish(fc_edata->filename, fc_edata->lineno, fc_edata->funcname);
}

/*
 * ReThrowError --- 重新抛出先前复制的错误
 *
 * 处理程序可以执行 CopyErrorData/FlushErrorState 以退出错误子系统，然后进行一些处理，最后 ReThrowError 重新抛出原始错误。这比仅使用 PG_RE_THROW() 更慢，但如果“某些处理”可能导致另一个错误，则应使用它。
 */
void ReThrowError(ErrorData *fc_edata)
{
	ErrorData  *fc_newedata;

	Assert(fc_edata->elevel == ERROR);

	/* 将数据推送回错误上下文 */
	recursion_depth++;
	MemoryContextSwitchTo(ErrorContext);

	if (++errordata_stack_depth >= ERRORDATA_STACK_SIZE)
	{
		/*
		 * 哎呀，栈不够大。我们将其视为PANIC条件，
		 * 因为这表明在错误恢复过程中发生了无限循环的错误。
		 */
		errordata_stack_depth = -1; /* 在栈上留出空间 */
		ereport(PANIC, (errmsg_internal("ERRORDATA_STACK_SIZE exceeded")));
	}

	fc_newedata = &errordata[errordata_stack_depth];
	memcpy(fc_newedata, fc_edata, sizeof(ErrorData));

	/* 复制分别分配的字段 */
	if (fc_newedata->message)
		fc_newedata->message = pstrdup(fc_newedata->message);
	if (fc_newedata->detail)
		fc_newedata->detail = pstrdup(fc_newedata->detail);
	if (fc_newedata->detail_log)
		fc_newedata->detail_log = pstrdup(fc_newedata->detail_log);
	if (fc_newedata->hint)
		fc_newedata->hint = pstrdup(fc_newedata->hint);
	if (fc_newedata->context)
		fc_newedata->context = pstrdup(fc_newedata->context);
	if (fc_newedata->backtrace)
		fc_newedata->backtrace = pstrdup(fc_newedata->backtrace);
	if (fc_newedata->schema_name)
		fc_newedata->schema_name = pstrdup(fc_newedata->schema_name);
	if (fc_newedata->table_name)
		fc_newedata->table_name = pstrdup(fc_newedata->table_name);
	if (fc_newedata->column_name)
		fc_newedata->column_name = pstrdup(fc_newedata->column_name);
	if (fc_newedata->datatype_name)
		fc_newedata->datatype_name = pstrdup(fc_newedata->datatype_name);
	if (fc_newedata->constraint_name)
		fc_newedata->constraint_name = pstrdup(fc_newedata->constraint_name);
	if (fc_newedata->internalquery)
		fc_newedata->internalquery = pstrdup(fc_newedata->internalquery);

	/* 将 assoc_context 重置为 ErrorContext */
	fc_newedata->assoc_context = ErrorContext;

	recursion_depth--;
	PG_RE_THROW();
}

/*
 * pg_re_throw --- PG_RE_THROW() 宏的独立实现
 */
void pg_re_throw(void)
{
	/* 如果可能，将错误抛给下一个外层 setjmp 处理程序 */
	if (PG_exception_stack != NULL)
		siglongjmp(*PG_exception_stack, 1);
	else
	{
		/*
		 * 如果我们到这里，elog(ERROR) 是在 PG_TRY 块内抛出的，我们现在已经退出该块，只发现没有外层 setjmp 处理程序来传递错误。如果错误一开始就抛出在块外，我们会将错误提升为 FATAL，因此正确的行为是现在将其变为 FATAL；也就是说，发出它然后调用 proc_exit。
		 */
		ErrorData  *fc_edata = &errordata[errordata_stack_depth];

		Assert(errordata_stack_depth >= 0);
		Assert(fc_edata->elevel == ERROR);
		fc_edata->elevel = FATAL;

		/*
		 * 至少在原则上，严重性增加可能改变输出决策的位置，因此重新计算。
		 */
		fc_edata->output_to_server = fc_should_output_to_server(FATAL);
		fc_edata->output_to_client = fc_should_output_to_client(FATAL);

		/*
		 * 我们可以使用 errfinish() 来处理其余部分，但我们不希望它第二次调用任何错误上下文例程。由于我们知道即将退出，清除上下文堆栈应该是可以的。
		 */
		error_context_stack = NULL;

		errfinish(fc_edata->filename, fc_edata->lineno, fc_edata->funcname);
	}

	/* 不返回 ... */
	ExceptionalCondition("pg_re_throw tried to return", "FailedAssertion",
						 __FILE__, __LINE__);
}


/*
 * GetErrorContextStack - 返回上下文栈，用于显示/诊断
 *
 * 返回一个在调用者上下文中的 pstrdup 字符串，其中包括 PG
 * 错误调用栈。调用者有责任确保在完成后对该字符串进行 pfree（或清理其上下文）。
 *
 * 通过遍历错误上下文并调用每个上下文的回调函数来收集这些信息，
 * 每个回调函数都应该调用 errcontext() 返回一个可以呈现给用户的字符串。
 */
char * GetErrorContextStack(void)
{
	ErrorData  *fc_edata;
	ErrorContextCallback *fc_econtext;

	/*
	 * 好吧，启动一个栈条目来存储信息。
	 */
	recursion_depth++;

	if (++errordata_stack_depth >= ERRORDATA_STACK_SIZE)
	{
		/*
		 * 哎呀，栈不够大。我们将其视为PANIC条件，
		 * 因为这表明在错误恢复过程中发生了无限循环的错误。
		 */
		errordata_stack_depth = -1; /* 在栈上留出空间 */
		ereport(PANIC, (errmsg_internal("ERRORDATA_STACK_SIZE exceeded")));
	}

	/*
	 * 到目前为止一切看起来不错，所以初始化我们的错误帧
	 */
	fc_edata = &errordata[errordata_stack_depth];
	MemSet(fc_edata, 0, sizeof(ErrorData));

	/*
	 * 将 assoc_context 设置为调用者的上下文，这样所做的任何分配
	 * （将包括 edata->context）都将使用他们的上下文。
	 */
	fc_edata->assoc_context = CurrentMemoryContext;

	/*
	 * 调用任何上下文回调函数以将上下文信息收集到 edata->context 中。
	 *
	 * 在回调函数中发生的错误应通过常规错误处理代码进行处理，
	 * 该代码应处理任何递归错误，尽管我们在上面进行了双重检查，以防万一。
	 */
	for (fc_econtext = error_context_stack;
		 fc_econtext != NULL;
		 fc_econtext = fc_econtext->previous)
		fc_econtext->callback(fc_econtext->arg);

	/*
	 * 从栈中清除自己，任何分配都应该使用 edata->assoc_context，
	 * 我们之前将其设置为调用者的上下文，因此我们可以自由地从栈中
	 * 移除我们的条目并减少递归深度然后退出。
	 */
	errordata_stack_depth--;
	recursion_depth--;

	/*
	 * 返回调用者请求的字符串的指针，该字符串应在他们的上下文中分配。
	 */
	return fc_edata->context;
}


/*
 * 错误输出文件的初始化
 */
void DebugFileOpen(void)
{
	int			fc_fd,
				fc_istty;

	if (OutputFileName[0])
	{
		/*
		 * 提供了一个调试输出文件名。
		 *
		 * 确保我们可以写入该文件，并找出它是否为 tty。
		 */
		if ((fc_fd = open(OutputFileName, O_CREAT | O_APPEND | O_WRONLY,
					   0666)) < 0)
			ereport(FATAL,
					(errcode_for_file_access(),
					 errmsg("could not open file \"%s\": %m", OutputFileName)));
		fc_istty = isatty(fc_fd);
		close(fc_fd);

		/*
		 * 将我们的 stderr 重定向到调试输出文件。
		 */
		if (!freopen(OutputFileName, "a", stderr))
			ereport(FATAL,
					(errcode_for_file_access(),
					 errmsg("could not reopen file \"%s\" as stderr: %m",
							OutputFileName)));

		/*
		 * 如果该文件是 tty 并且我们在 postmaster 下运行，尝试将
		 * stdout 也发送到那里（如果它不是 tty，则 stderr 将阻塞
		 * stdout，因此我们不妨让 stdout 去它之前要去的地方）。
		 */
		if (fc_istty && IsUnderPostmaster)
			if (!freopen(OutputFileName, "a", stdout))
				ereport(FATAL,
						(errcode_for_file_access(),
						 errmsg("could not reopen file \"%s\" as stdout: %m",
								OutputFileName)));
	}
}


#ifdef HAVE_SYSLOG

/*
 * 设置或更新 syslog 记录的参数
 */
void set_syslog_parameters(const char *fc_ident, int fc_facility)
{
	/*
	 * guc.c 可能会重复调用我们，使用相同的参数，因此不必
	 * 不必要地冲突 syslog 连接。此外，我们不会重新打开
	 * 连接，直到需要时，因为此例程将被调用，无论 Log_destination
	 * 实际上是否提到了 syslog。
	 *
	 * 注意，我们会自己复制 ident 字符串，而不是依赖于 guc.c 的。这
	 * 可能过于小心，但可以确保我们不会意外释放 syslog 仍在使用的字符串。
	 */
	if (syslog_ident == NULL || strcmp(syslog_ident, fc_ident) != 0 ||
		syslog_facility != fc_facility)
	{
		if (openlog_done)
		{
			closelog();
			openlog_done = false;
		}
		if (syslog_ident)
			free(syslog_ident);
		syslog_ident = strdup(fc_ident);
		/* 如果 strdup 失败，我们将在 write_syslog() 中处理 */
		syslog_facility = fc_facility;
	}
}


/*
 * 将消息行写入 syslog
 */
static void fc_write_syslog(int fc_level, const char *fc_line)
{
	static unsigned long fc_seq = 0;

	int			fc_len;
	const char *fc_nlpos;

	/* 如果尚未打开 syslog 连接，则打开 */
	if (!openlog_done)
	{
		openlog(syslog_ident ? syslog_ident : "postgres",
				LOG_PID | LOG_NDELAY | LOG_NOWAIT,
				syslog_facility);
		openlog_done = true;
	}

	/*
	 * 我们为每条日志消息添加一个序列号，以抑制“相同”的
	 * 消息。
	 */
	fc_seq++;

	/*
	 * 我们的问题是许多 syslog 实现对长消息的处理方式不可接受。
	 * 尽管这个函数无法解决这个问题，但它通过将消息分割成较小的部分来变通。
	 *
	 * 如果消息太长或者消息包含嵌入的换行符，我们将分成多个 syslog() 调用。
	 */
	fc_len = strlen(fc_line);
	fc_nlpos = strchr(fc_line, '\n');
	if (syslog_split_messages && (fc_len > PG_SYSLOG_LIMIT || fc_nlpos != NULL))
	{
		int			fc_chunk_nr = 0;

		while (fc_len > 0)
		{
			char		fc_buf[PG_SYSLOG_LIMIT + 1];
			int			fc_buflen;
			int			fc_i;

			/* 如果我们以换行符开始，向前移动一个字符 */
			if (fc_line[0] == '\n')
			{
				fc_line++;
				fc_len--;
				/* 我们还需要重新计算下一个换行符的位置 */
				fc_nlpos = strchr(fc_line, '\n');
				continue;
			}

			/* 将一行或尽可能多的内容复制到 buf 中 */
			if (fc_nlpos != NULL)
				fc_buflen = fc_nlpos - fc_line;
			else
				fc_buflen = fc_len;
			fc_buflen = Min(fc_buflen, PG_SYSLOG_LIMIT);
			memcpy(fc_buf, fc_line, fc_buflen);
			fc_buf[fc_buflen] = '\0';

			/* 修剪到多字节字母边界 */
			fc_buflen = pg_mbcliplen(fc_buf, fc_buflen, fc_buflen);
			if (fc_buflen <= 0)
				return;
			fc_buf[fc_buflen] = '\0';

			/* 已经是字边界了吗？ */
			if (fc_line[fc_buflen] != '\0' &&
				!isspace((unsigned char) fc_line[fc_buflen]))
			{
				/* 尝试在字边界处分割 */
				fc_i = fc_buflen - 1;
				while (fc_i > 0 && !isspace((unsigned char) fc_buf[fc_i]))
					fc_i--;

				if (fc_i > 0)		/* 其他情况下无法分割字边界 */
				{
					fc_buflen = fc_i;
					fc_buf[fc_i] = '\0';
				}
			}

			fc_chunk_nr++;

			if (syslog_sequence_numbers)
				syslog(fc_level, "[%lu-%d] %s", fc_seq, fc_chunk_nr, fc_buf);
			else
				syslog(fc_level, "[%d] %s", fc_chunk_nr, fc_buf);

			fc_line += fc_buflen;
			fc_len -= fc_buflen;
		}
	}
	else
	{
		/* 消息足够短 */
		if (syslog_sequence_numbers)
			syslog(fc_level, "[%lu] %s", fc_seq, fc_line);
		else
			syslog(fc_level, "%s", fc_line);
	}
}
#endif							/* HAVE_SYSLOG */

#ifdef WIN32
/*
 * 获取与 Windows ANSI 代码页等效的 PostgreSQL 值。 “ANSI” 系统
 * 接口（例如 CreateFileA()）期望此编码中的字符串参数。
 * 在给定系统中的每个进程始终会找到相同的值。
 */
static int GetACPEncoding(void)
{
	static int	encoding = -2;

	if (encoding == -2)
		encoding = pg_codepage_to_encoding(GetACP());

	return encoding;
}

/*
 * 将消息行写入 Windows 事件日志
 */
static void write_eventlog(int level, const char *line, int len)
{
	WCHAR	   *utf16;
	int			eventlevel = EVENTLOG_ERROR_TYPE;
	static HANDLE evtHandle = INVALID_HANDLE_VALUE;

	if (evtHandle == INVALID_HANDLE_VALUE)
	{
		evtHandle = RegisterEventSource(NULL,
										event_source ? event_source : DEFAULT_EVENT_SOURCE);
		if (evtHandle == NULL)
		{
			evtHandle = INVALID_HANDLE_VALUE;
			return;
		}
	}

	switch (level)
	{
		case DEBUG5:
		case DEBUG4:
		case DEBUG3:
		case DEBUG2:
		case DEBUG1:
		case LOG:
		case LOG_SERVER_ONLY:
		case INFO:
		case NOTICE:
			eventlevel = EVENTLOG_INFORMATION_TYPE;
			break;
		case WARNING:
		case WARNING_CLIENT_ONLY:
			eventlevel = EVENTLOG_WARNING_TYPE;
			break;
		case ERROR:
		case FATAL:
		case PANIC:
		default:
			eventlevel = EVENTLOG_ERROR_TYPE;
			break;
	}

	/*
	 * 如果消息字符编码与 ReportEventA() 期望的编码匹配，请调用
	 * 它以避免转换的危险。否则，尝试将消息转换为 UTF16 并使用
	 * ReportEventW() 写入。 如果转换失败，则退回使用 ReportEventA()。
	 *
	 * 由于我们为转换分配了所需的结构，因此如果我们尚未设置
	 * CurrentMemoryContext 也会退回到写入未转换的内容。
	 *
	 * 还要验证我们是否没有因在 pgwin32_message_to_UTF16() 深处
	 * 抛出的错误消息而陷入错误递归问题。
	 */
	if (!in_error_recursion_trouble() &&
		CurrentMemoryContext != NULL &&
		GetMessageEncoding() != GetACPEncoding())
	{
		utf16 = pgwin32_message_to_UTF16(line, len, NULL);
		if (utf16)
		{
			ReportEventW(evtHandle,
						 eventlevel,
						 0,
						 0,		/* 所有事件的 ID 为 0 */
						 NULL,
						 1,
						 0,
						 (LPCWSTR *) &utf16,
						 NULL);
			/* XXX 当 ReportEventW() 失败时尝试 ReportEventA()？ */

			pfree(utf16);
			return;
		}
	}
	ReportEventA(evtHandle,
				 eventlevel,
				 0,
				 0,				/* 所有事件的 ID 为 0 */
				 NULL,
				 1,
				 0,
				 &line,
				 NULL);
}
#endif							/* WIN32 */

static void fc_write_console(const char *fc_line, int fc_len)
{
	int			fc_rc;

#ifdef WIN32

	/*
	 * 尝试将消息转换为 UTF16，并使用 WriteConsoleW() 写入。
	 * 如果出现任何故障，则退回使用 write()。
	 *
	 * 与 write_eventlog() 不同，不会直接基于适用的编码跳转到
	 * write()。与 WriteConsoleW() 不同，write() 依赖于控制台输出代码页的适用性。
	 * 由于我们在 SubPostmasterMain() 中将 stderr 设置为二进制模式，
	 * write() 无论如何都会跳过必要的转换。
	 *
	 * 如果 stderr 被重定向，WriteConsoleW() 将失败，因此在这种情况下
	 * 只能写入未转换的日志文件。
	 *
	 * 由于我们为转换分配了所需的结构，如果尚未设置
	 * CurrentMemoryContext 也会退回到写入未转换的内容。
	 */
	if (!in_error_recursion_trouble() &&
		!redirection_done &&
		CurrentMemoryContext != NULL)
	{
		WCHAR	   *utf16;
		int			utf16len;

		utf16 = pgwin32_message_to_UTF16(line, len, &utf16len);
		if (utf16 != NULL)
		{
			HANDLE		stdHandle;
			DWORD		written;

			stdHandle = GetStdHandle(STD_ERROR_HANDLE);
			if (WriteConsoleW(stdHandle, utf16, utf16len, &written, NULL))
			{
				pfree(utf16);
				return;
			}

			/*
			 * 如果 WriteConsoleW() 失败，则退回到写入
			 * 未转换的消息。
			 */
			pfree(utf16);
		}
	}
#else

	/*
	 * 在非win32平台上的转换尚未实现。需要
	 * 非抛出版本的pg_do_encoding_conversion()，该函数将
	 * 无法转换的字符转换为'?'而不发生错误。
	 *
	 * XXX：我们现在有一个不抛出版本。但它不会转换为'?'。
	 */
#endif

	/*
	 * 我们在这里忽略write()的任何错误。我们没有有效的方式来报告
	 * 它……显然在stderr上抱怨不太可能有成效。
	 */
	fc_rc = write(fileno(stderr), fc_line, fc_len);
	(void) fc_rc;
}

/*
 * get_formatted_log_time -- 计算并获取日志时间戳。
 *
 * 如果尚未设置，则计算时间戳，以便它在所有需要
 * 保持一致的日志目标之间保持一致。注意，计算出的
 * 时间戳是在静态缓冲区中返回，而不是在palloc()中返回。
 */
char * get_formatted_log_time(void)
{
	pg_time_t	fc_stamp_time;
	char		fc_msbuf[13];

	/* 如果已经计算，则返回 */
	if (formatted_log_time[0] != '\0')
		return formatted_log_time;

	if (!saved_timeval_set)
	{
		gettimeofday(&saved_timeval, NULL);
		saved_timeval_set = true;
	}

	fc_stamp_time = (pg_time_t) saved_timeval.tv_sec;

	/*
	 * 注意：我们期望guc.c将在Log_line_prefix变得
	 * 非空或选择CSV模式之前确保设置log_timezone（
	 * 至少设置一个最小的GMT值）。
	 */
	pg_strftime(formatted_log_time, FORMATTED_TS_LEN,
	/* 为毫秒留出空间... */
				"%Y-%m-%d %H:%M:%S     %Z",
				pg_localtime(&fc_stamp_time, log_timezone));

	/* 将毫秒'粘贴'到适当位置... */
	sprintf(fc_msbuf, ".%03d", (int) (saved_timeval.tv_usec / 1000));
	memcpy(formatted_log_time + 19, fc_msbuf, 4);

	return formatted_log_time;
}

/*
 * reset_formatted_start_time -- 重置开始时间戳
 */
void reset_formatted_start_time(void)
{
	formatted_start_time[0] = '\0';
}

/*
 * get_formatted_start_time -- 计算并获取开始时间戳。
 *
 * 如果尚未设置，则计算时间戳。注意，计算出的
 * 时间戳是在静态缓冲区中返回，而不是在palloc()中返回。
 */
char * get_formatted_start_time(void)
{
	pg_time_t	fc_stamp_time = (pg_time_t) MyStartTime;

	/* 如果已经计算，则返回 */
	if (formatted_start_time[0] != '\0')
		return formatted_start_time;

	/*
	 * 注意：我们期望guc.c将在Log_line_prefix变得
	 * 非空或选择CSV模式之前确保设置log_timezone（
	 * 至少设置一个最小的GMT值）。
	 */
	pg_strftime(formatted_start_time, FORMATTED_TS_LEN,
				"%Y-%m-%d %H:%M:%S %Z",
				pg_localtime(&fc_stamp_time, log_timezone));

	return formatted_start_time;
}

/*
 * check_log_of_query -- 检查查询是否可以被记录
 */
bool check_log_of_query(ErrorData *fc_edata)
{
	/* 是否需要记录？ */
	if (!fc_is_log_level_output(fc_edata->elevel, log_min_error_statement))
		return false;

	/* 是否希望记录查询？ */
	if (fc_edata->hide_stmt)
		return false;

	/* 查询字符串可用吗？ */
	if (debug_query_string == NULL)
		return false;

	return true;
}

/*
 * get_backend_type_for_log -- 日志条目的后端类型
 *
 * 返回指向静态缓冲区的指针，而不是在palloc()中返回。
 */
const char * get_backend_type_for_log(void)
{
	const char *fc_backend_type_str;

	if (MyProcPid == PostmasterPid)
		fc_backend_type_str = "postmaster";
	else if (MyBackendType == B_BG_WORKER)
		fc_backend_type_str = MyBgworkerEntry->bgw_type;
	else
		fc_backend_type_str = GetBackendTypeDesc(MyBackendType);

	return fc_backend_type_str;
}

/*
 * process_log_prefix_padding --- 处理log_line_prefix中格式
 * 字符串的辅助函数
 *
 * 注意：如果此函数在格式字符串中发现它认为无效的内容，
 * 则返回NULL。
 */
static const char * fc_process_log_prefix_padding(const char *fc_p, int *fc_ppadding)
{
	int			fc_paddingsign = 1;
	int			fc_padding = 0;

	if (*fc_p == '-')
	{
		fc_p++;

		if (*fc_p == '\0')			/* buf是否以%-结尾？ */
			return NULL;
		fc_paddingsign = -1;
	}

	/* 生成数字字符串的整数版本 */
	while (*fc_p >= '0' && *fc_p <= '9')
		fc_padding = fc_padding * 10 + (*fc_p++ - '0');

	/* 如果它以填充数字结尾，则格式无效 */
	if (*fc_p == '\0')
		return NULL;

	fc_padding *= fc_paddingsign;
	*fc_ppadding = fc_padding;
	return fc_p;
}

/*
 * 为日志行格式标签信息；附加到提供的缓冲区。
 */
static void fc_log_line_prefix(StringInfo fc_buf, ErrorData *fc_edata)
{
	/* 行号的静态计数器 */
	static long fc_log_line_number = 0;

	/* 当前进程中的计数器是否已重置？ */
	static int	fc_log_my_pid = 0;
	int			fc_padding;
	const char *fc_p;

	/*
	 * 这是少数几个我们宁愿不从postmaster继承静态
	 * 变量值的地方之一。但由于我们将这样做，当
	 * MyProcPid发生变化时重置它。MyStartTime也会
	 * 在MyProcPid变化时变化，因此也重置格式化的
	 * 开始时间戳。
	 */
	if (fc_log_my_pid != MyProcPid)
	{
		fc_log_line_number = 0;
		fc_log_my_pid = MyProcPid;
		reset_formatted_start_time();
	}
	fc_log_line_number++;

	if (Log_line_prefix == NULL)
		return;					/* 以防guc尚未运行 */

	for (fc_p = Log_line_prefix; *fc_p != '\0'; fc_p++)
	{
		if (*fc_p != '%')
		{
			/* 字面字符，仅复制 */
			appendStringInfoChar(fc_buf, *fc_p);
			continue;
		}

		/* 必须是 '%', 所以跳到下一个字符 */
		fc_p++;
		if (*fc_p == '\0')
			break;				/* 格式错误 - 忽略它 */
		else if (*fc_p == '%')
		{
			/* 字符串包含 %% */
			appendStringInfoChar(fc_buf, '%');
			continue;
		}


		/*
		 * 处理可能存在于 '%' 之后的任何格式。请注意，
		 * process_log_prefix_padding 会在存在填充数字时将 p 移动到填充数字之后。
		 *
		 * 注意：由于只有 '-', '0' 到 '9' 是有效的格式字符，
		 * 我们可以在这里进行快速检查以预检格式。如果字符
		 * 不是格式，则可以跳过一次无用的函数调用。
		 *
		 * 进一步注意：至少在某些平台上，传递 %*s 而不是
		 * %s 给 appendStringInfo() 的速度大大降低，因此下面的
		 * 大多数情况下会避免这么做，除非实际上指定了非零填充。
		 */
		if (*fc_p > '9')
			fc_padding = 0;
		else if ((fc_p = fc_process_log_prefix_padding(fc_p, &fc_padding)) == NULL)
			break;

		/* 处理选项 */
		switch (*fc_p)
		{
			case 'a':
				if (MyProcPort)
				{
					const char *fc_appname = application_name;

					if (fc_appname == NULL || *fc_appname == '\0')
						fc_appname = _("[unknown]");
					if (fc_padding != 0)
						appendStringInfo(fc_buf, "%*s", fc_padding, fc_appname);
					else
						appendStringInfoString(fc_buf, fc_appname);
				}
				else if (fc_padding != 0)
					appendStringInfoSpaces(fc_buf,
										   fc_padding > 0 ? fc_padding : -fc_padding);

				break;
			case 'b':
				{
					const char *fc_backend_type_str = get_backend_type_for_log();

					if (fc_padding != 0)
						appendStringInfo(fc_buf, "%*s", fc_padding, fc_backend_type_str);
					else
						appendStringInfoString(fc_buf, fc_backend_type_str);
					break;
				}
			case 'u':
				if (MyProcPort)
				{
					const char *fc_username = MyProcPort->user_name;

					if (fc_username == NULL || *fc_username == '\0')
						fc_username = _("[unknown]");
					if (fc_padding != 0)
						appendStringInfo(fc_buf, "%*s", fc_padding, fc_username);
					else
						appendStringInfoString(fc_buf, fc_username);
				}
				else if (fc_padding != 0)
					appendStringInfoSpaces(fc_buf,
										   fc_padding > 0 ? fc_padding : -fc_padding);
				break;
			case 'd':
				if (MyProcPort)
				{
					const char *fc_dbname = MyProcPort->database_name;

					if (fc_dbname == NULL || *fc_dbname == '\0')
						fc_dbname = _("[unknown]");
					if (fc_padding != 0)
						appendStringInfo(fc_buf, "%*s", fc_padding, fc_dbname);
					else
						appendStringInfoString(fc_buf, fc_dbname);
				}
				else if (fc_padding != 0)
					appendStringInfoSpaces(fc_buf,
										   fc_padding > 0 ? fc_padding : -fc_padding);
				break;
			case 'c':
				if (fc_padding != 0)
				{
					char		fc_strfbuf[128];

					snprintf(fc_strfbuf, sizeof(fc_strfbuf) - 1, "%" INT64_MODIFIER "x.%x",
							 MyStartTime, MyProcPid);
					appendStringInfo(fc_buf, "%*s", fc_padding, fc_strfbuf);
				}
				else
					appendStringInfo(fc_buf, "%" INT64_MODIFIER "x.%x", MyStartTime, MyProcPid);
				break;
			case 'p':
				if (fc_padding != 0)
					appendStringInfo(fc_buf, "%*d", fc_padding, MyProcPid);
				else
					appendStringInfo(fc_buf, "%d", MyProcPid);
				break;

			case 'P':
				if (MyProc)
				{
					PGPROC	   *fc_leader = MyProc->lockGroupLeader;

					/*
					 * 仅为活跃的并行工作者显示领导者。这
					 * 排除了并行组的领导者。
					 */
					if (fc_leader == NULL || fc_leader->pid == MyProcPid)
						appendStringInfoSpaces(fc_buf,
											   fc_padding > 0 ? fc_padding : -fc_padding);
					else if (fc_padding != 0)
						appendStringInfo(fc_buf, "%*d", fc_padding, fc_leader->pid);
					else
						appendStringInfo(fc_buf, "%d", fc_leader->pid);
				}
				else if (fc_padding != 0)
					appendStringInfoSpaces(fc_buf,
										   fc_padding > 0 ? fc_padding : -fc_padding);
				break;

			case 'l':
				if (fc_padding != 0)
					appendStringInfo(fc_buf, "%*ld", fc_padding, fc_log_line_number);
				else
					appendStringInfo(fc_buf, "%ld", fc_log_line_number);
				break;
			case 'm':
				/* 强制重置日志时间戳 */
				formatted_log_time[0] = '\0';
				(void) get_formatted_log_time();

				if (fc_padding != 0)
					appendStringInfo(fc_buf, "%*s", fc_padding, formatted_log_time);
				else
					appendStringInfoString(fc_buf, formatted_log_time);
				break;
			case 't':
				{
					pg_time_t	fc_stamp_time = (pg_time_t) time(NULL);
					char		fc_strfbuf[128];

					pg_strftime(fc_strfbuf, sizeof(fc_strfbuf),
								"%Y-%m-%d %H:%M:%S %Z",
								pg_localtime(&fc_stamp_time, log_timezone));
					if (fc_padding != 0)
						appendStringInfo(fc_buf, "%*s", fc_padding, fc_strfbuf);
					else
						appendStringInfoString(fc_buf, fc_strfbuf);
				}
				break;
			case 'n':
				{
					char		fc_strfbuf[128];

					if (!saved_timeval_set)
					{
						gettimeofday(&saved_timeval, NULL);
						saved_timeval_set = true;
					}

					snprintf(fc_strfbuf, sizeof(fc_strfbuf), "%ld.%03d",
							 (long) saved_timeval.tv_sec,
							 (int) (saved_timeval.tv_usec / 1000));

					if (fc_padding != 0)
						appendStringInfo(fc_buf, "%*s", fc_padding, fc_strfbuf);
					else
						appendStringInfoString(fc_buf, fc_strfbuf);
				}
				break;
			case 's':
				{
					char	   *fc_start_time = get_formatted_start_time();

					if (fc_padding != 0)
						appendStringInfo(fc_buf, "%*s", fc_padding, fc_start_time);
					else
						appendStringInfoString(fc_buf, fc_start_time);
				}
				break;
			case 'i':
				if (MyProcPort)
				{
					const char *fc_psdisp;
					int			fc_displen;

					fc_psdisp = get_ps_display(&fc_displen);
					if (fc_padding != 0)
						appendStringInfo(fc_buf, "%*s", fc_padding, fc_psdisp);
					else
						appendBinaryStringInfo(fc_buf, fc_psdisp, fc_displen);
				}
				else if (fc_padding != 0)
					appendStringInfoSpaces(fc_buf,
										   fc_padding > 0 ? fc_padding : -fc_padding);
				break;
			case 'r':
				if (MyProcPort && MyProcPort->remote_host)
				{
					if (fc_padding != 0)
					{
						if (MyProcPort->remote_port && MyProcPort->remote_port[0] != '\0')
						{
							/*
							 * 这个选项稍微特殊，因为端口
							 * 号可能附加在末尾。这里我们
							 * 需要构建一个包含 remote_host
							 * 和可选的 remote_port（如果
							 * 设置）的一字符串，以便我们可以
							 * 正确对齐字符串。
							 */

							char	   *fc_hostport;

							fc_hostport = psprintf("%s(%s)", MyProcPort->remote_host, MyProcPort->remote_port);
							appendStringInfo(fc_buf, "%*s", fc_padding, fc_hostport);
							pfree(fc_hostport);
						}
						else
							appendStringInfo(fc_buf, "%*s", fc_padding, MyProcPort->remote_host);
					}
					else
					{
						/* 填充为 0，因此我们不需要临时缓冲区 */
						appendStringInfoString(fc_buf, MyProcPort->remote_host);
						if (MyProcPort->remote_port &&
							MyProcPort->remote_port[0] != '\0')
							appendStringInfo(fc_buf, "(%s)",
											 MyProcPort->remote_port);
					}
				}
				else if (fc_padding != 0)
					appendStringInfoSpaces(fc_buf,
										   fc_padding > 0 ? fc_padding : -fc_padding);
				break;
			case 'h':
				if (MyProcPort && MyProcPort->remote_host)
				{
					if (fc_padding != 0)
						appendStringInfo(fc_buf, "%*s", fc_padding, MyProcPort->remote_host);
					else
						appendStringInfoString(fc_buf, MyProcPort->remote_host);
				}
				else if (fc_padding != 0)
					appendStringInfoSpaces(fc_buf,
										   fc_padding > 0 ? fc_padding : -fc_padding);
				break;
			case 'q':
				/* 在 postmaster 和相关进程中，如果看到 %q 则停止 */
				/* 在后端，只需忽略 */
				if (MyProcPort == NULL)
					return;
				break;
			case 'v':
				/* 保持VXID格式与lockfuncs.c同步 */
				if (MyProc != NULL && MyProc->backendId != InvalidBackendId)
				{
					if (fc_padding != 0)
					{
						char		fc_strfbuf[128];

						snprintf(fc_strfbuf, sizeof(fc_strfbuf) - 1, "%d/%u",
								 MyProc->backendId, MyProc->lxid);
						appendStringInfo(fc_buf, "%*s", fc_padding, fc_strfbuf);
					}
					else
						appendStringInfo(fc_buf, "%d/%u", MyProc->backendId, MyProc->lxid);
				}
				else if (fc_padding != 0)
					appendStringInfoSpaces(fc_buf,
										   fc_padding > 0 ? fc_padding : -fc_padding);
				break;
			case 'x':
				if (fc_padding != 0)
					appendStringInfo(fc_buf, "%*u", fc_padding, GetTopTransactionIdIfAny());
				else
					appendStringInfo(fc_buf, "%u", GetTopTransactionIdIfAny());
				break;
			case 'e':
				if (fc_padding != 0)
					appendStringInfo(fc_buf, "%*s", fc_padding, unpack_sql_state(fc_edata->sqlerrcode));
				else
					appendStringInfoString(fc_buf, unpack_sql_state(fc_edata->sqlerrcode));
				break;
			case 'Q':
				if (fc_padding != 0)
					appendStringInfo(fc_buf, "%*lld", fc_padding,
									 (long long) pgstat_get_my_query_id());
				else
					appendStringInfo(fc_buf, "%lld",
									 (long long) pgstat_get_my_query_id());
				break;
			default:
				/* 格式错误 - 忽略它 */
				break;
		}
	}
}

/*
 * 解包 MAKE_SQLSTATE 代码。注意，这返回指向一个
 * 静态缓冲区的指针。
 */
char * unpack_sql_state(int fc_sql_state)
{
	static char fc_buf[12];
	int			fc_i;

	for (fc_i = 0; fc_i < 5; fc_i++)
	{
		fc_buf[fc_i] = PGUNSIXBIT(fc_sql_state);
		fc_sql_state >>= 6;
	}

	fc_buf[fc_i] = '\0';
	return fc_buf;
}


/*
 * 将错误报告写入服务器日志
 */
static void fc_send_message_to_server_log(ErrorData *fc_edata)
{
	StringInfoData fc_buf;
	bool		fc_fallback_to_stderr = false;

	initStringInfo(&fc_buf);

	saved_timeval_set = false;
	formatted_log_time[0] = '\0';

	fc_log_line_prefix(&fc_buf, fc_edata);
	appendStringInfo(&fc_buf, "%s:  ", _(error_severity(fc_edata->elevel)));

	if (Log_error_verbosity >= PGERROR_VERBOSE)
		appendStringInfo(&fc_buf, "%s: ", unpack_sql_state(fc_edata->sqlerrcode));

	if (fc_edata->message)
		fc_append_with_tabs(&fc_buf, fc_edata->message);
	else
		fc_append_with_tabs(&fc_buf, _("missing error text"));

	if (fc_edata->cursorpos > 0)
		appendStringInfo(&fc_buf, _(" at character %d"),
						 fc_edata->cursorpos);
	else if (fc_edata->internalpos > 0)
		appendStringInfo(&fc_buf, _(" at character %d"),
						 fc_edata->internalpos);

	appendStringInfoChar(&fc_buf, '\n');

	if (Log_error_verbosity >= PGERROR_DEFAULT)
	{
		if (fc_edata->detail_log)
		{
			fc_log_line_prefix(&fc_buf, fc_edata);
			appendStringInfoString(&fc_buf, _("DETAIL:  "));
			fc_append_with_tabs(&fc_buf, fc_edata->detail_log);
			appendStringInfoChar(&fc_buf, '\n');
		}
		else if (fc_edata->detail)
		{
			fc_log_line_prefix(&fc_buf, fc_edata);
			appendStringInfoString(&fc_buf, _("DETAIL:  "));
			fc_append_with_tabs(&fc_buf, fc_edata->detail);
			appendStringInfoChar(&fc_buf, '\n');
		}
		if (fc_edata->hint)
		{
			fc_log_line_prefix(&fc_buf, fc_edata);
			appendStringInfoString(&fc_buf, _("HINT:  "));
			fc_append_with_tabs(&fc_buf, fc_edata->hint);
			appendStringInfoChar(&fc_buf, '\n');
		}
		if (fc_edata->internalquery)
		{
			fc_log_line_prefix(&fc_buf, fc_edata);
			appendStringInfoString(&fc_buf, _("QUERY:  "));
			fc_append_with_tabs(&fc_buf, fc_edata->internalquery);
			appendStringInfoChar(&fc_buf, '\n');
		}
		if (fc_edata->context && !fc_edata->hide_ctx)
		{
			fc_log_line_prefix(&fc_buf, fc_edata);
			appendStringInfoString(&fc_buf, _("CONTEXT:  "));
			fc_append_with_tabs(&fc_buf, fc_edata->context);
			appendStringInfoChar(&fc_buf, '\n');
		}
		if (Log_error_verbosity >= PGERROR_VERBOSE)
		{
			/* 假设 funcname 或 filename 中没有换行符... */
			if (fc_edata->funcname && fc_edata->filename)
			{
				fc_log_line_prefix(&fc_buf, fc_edata);
				appendStringInfo(&fc_buf, _("LOCATION:  %s, %s:%d\n"),
								 fc_edata->funcname, fc_edata->filename,
								 fc_edata->lineno);
			}
			else if (fc_edata->filename)
			{
				fc_log_line_prefix(&fc_buf, fc_edata);
				appendStringInfo(&fc_buf, _("LOCATION:  %s:%d\n"),
								 fc_edata->filename, fc_edata->lineno);
			}
		}
		if (fc_edata->backtrace)
		{
			fc_log_line_prefix(&fc_buf, fc_edata);
			appendStringInfoString(&fc_buf, _("BACKTRACE:  "));
			fc_append_with_tabs(&fc_buf, fc_edata->backtrace);
			appendStringInfoChar(&fc_buf, '\n');
		}
	}

	/*
	 * 如果用户希望记录生成此错误的查询，则进行记录。
	 */
	if (check_log_of_query(fc_edata))
	{
		fc_log_line_prefix(&fc_buf, fc_edata);
		appendStringInfoString(&fc_buf, _("STATEMENT:  "));
		fc_append_with_tabs(&fc_buf, debug_query_string);
		appendStringInfoChar(&fc_buf, '\n');
	}

#ifdef HAVE_SYSLOG
	/* 写入 syslog（如果启用） */
	if (Log_destination & LOG_DESTINATION_SYSLOG)
	{
		int			fc_syslog_level;

		switch (fc_edata->elevel)
		{
			case DEBUG5:
			case DEBUG4:
			case DEBUG3:
			case DEBUG2:
			case DEBUG1:
				fc_syslog_level = LOG_DEBUG;
				break;
			case LOG:
			case LOG_SERVER_ONLY:
			case INFO:
				fc_syslog_level = LOG_INFO;
				break;
			case NOTICE:
			case WARNING:
			case WARNING_CLIENT_ONLY:
				fc_syslog_level = LOG_NOTICE;
				break;
			case ERROR:
				fc_syslog_level = LOG_WARNING;
				break;
			case FATAL:
				fc_syslog_level = LOG_ERR;
				break;
			case PANIC:
			default:
				fc_syslog_level = LOG_CRIT;
				break;
		}

		fc_write_syslog(fc_syslog_level, fc_buf.data);
	}
#endif							/* HAVE_SYSLOG */

#ifdef WIN32
	/* 写入 eventlog（如果启用） */
	if (Log_destination & LOG_DESTINATION_EVENTLOG)
	{
		write_eventlog(fc_edata->elevel, fc_buf.data, fc_buf.len);
	}
#endif							/* WIN32 */

	/* 写入 csvlog（如果启用） */
	if (Log_destination & LOG_DESTINATION_CSVLOG)
	{
		/*
		 * 如果安全可以发送 CSV 数据（syslogger 不需要
		 * 管道）。如果这不可能，退回到写入
		 * stderr 的条目。
		 */
		if (redirection_done || MyBackendType == B_LOGGER)
			write_csvlog(fc_edata);
		else
			fc_fallback_to_stderr = true;
	}

	/* 写入 JSON 日志（如果启用） */
	if (Log_destination & LOG_DESTINATION_JSONLOG)
	{
		/*
		 * 如果安全可以发送 JSON 数据（syslogger 不需要
		 * 管道）。如果这不可能，退回到写入
		 * stderr 的条目。
		 */
		if (redirection_done || MyBackendType == B_LOGGER)
		{
			write_jsonlog(fc_edata);
		}
		else
			fc_fallback_to_stderr = true;
	}

	/*
	 * 如果启用或由于先前的限制而需要，
	 * 则写入 stderr。
	 */
	if ((Log_destination & LOG_DESTINATION_STDERR) ||
		whereToSendOutput == DestDebug ||
		fc_fallback_to_stderr)
	{
		/*
		 * 如果我们知道 syslogger 应该捕获 stderr 输出，
		 * 并且我们自己不是 syslogger，则使用分块协议。
		 * 否则，直接写入 stderr。
		 */
		if (redirection_done && MyBackendType != B_LOGGER)
			write_pipe_chunks(fc_buf.data, fc_buf.len, LOG_DESTINATION_STDERR);
#ifdef WIN32

		/*
		 * 在 win32 服务环境中，没有可用的 stderr。
		 * 捕获任何输出并写入 eventlog。
		 *
		 * 如果 stderr 重定向已激活，则上面写入 stderr 是
		 * 可以的，因为这实际上是一个发送到 syslogger 进程的管道。
		 */
		else if (pgwin32_is_service())
			write_eventlog(fc_edata->elevel, fc_buf.data, fc_buf.len);
#endif
		else
			fc_write_console(fc_buf.data, fc_buf.len);
	}

	/* 如果在syslogger进程中，尝试直接写入文件的消息 */
	if (MyBackendType == B_LOGGER)
		write_syslogger_file(fc_buf.data, fc_buf.len, LOG_DESTINATION_STDERR);

	/* 不再需要格式化为 stderr 的消息 */
	pfree(fc_buf.data);
}

/*
 * 使用分块协议将数据发送到 syslogger
 *
 * 注意：当多个后端写入 syslogger 管道时，确保每次写入都完整且不与其他进程的数据交错是至关重要的。幸运的是，POSIX 规范要求对管道的写入是原子的，只要不超过 PIPE_BUF 字节。因此，我们将长消息分为不超过该长度的块，并每次写入调用发送一个块。收集进程知道如何重新组装这些块。
 *
 * 由于原子写入的要求，这里的 write() 只有两个可能的结果：-1 表示失败，或者请求的字节数。对于失败，我们实际上无能为力；重试可能陷入无限循环，而且我们甚至无法有效地报告错误。（我们没有其他地方可以发送它！）所以我们就忽略 write() 的结果。然而，在某些平台上，忽略 write() 的结果会引发编译器警告，所以我们可以稍微做点手脚，将 rc 转换为 void 来让编译器安静。
 */
void write_pipe_chunks(char *fc_data, int fc_len, int fc_dest)
{
	PipeProtoChunk fc_p;
	int			fc_fd = fileno(stderr);
	int			fc_rc;

#ifdef FDD
	if(fc_dest == LOG_DESTINATION_AUDIT && !Logging_collector)
	{
		if(syslogPipe[1] <= 0)
		{
			//启动好服务时未开启审计，会话一直保持的情况下，开启审计，此时syslogger进程未启动
			return;
		}
		fc_fd = syslogPipe[1];
	}
#endif
	Assert(fc_len > 0);

	fc_p.proto.nuls[0] = fc_p.proto.nuls[1] = '\0';
	fc_p.proto.pid = MyProcPid;
	fc_p.proto.flags = 0;
	if (fc_dest == LOG_DESTINATION_STDERR)
		fc_p.proto.flags |= PIPE_PROTO_DEST_STDERR;
	else if (fc_dest == LOG_DESTINATION_CSVLOG)
		fc_p.proto.flags |= PIPE_PROTO_DEST_CSVLOG;
	else if (fc_dest == LOG_DESTINATION_JSONLOG)
		fc_p.proto.flags |= PIPE_PROTO_DEST_JSONLOG;
#ifdef FDD
	else if (fc_dest == LOG_DESTINATION_AUDIT)
	{
		fc_p.proto.flags |= PIPE_PROTO_DEST_AUDIT;//表明写入的是审计日志，而不是系统日志
	}	
#endif
	/* 写入除最后一块之外的所有块 */
	while (fc_len > PIPE_MAX_PAYLOAD)
	{
		/* 还不需要设置 PIPE_PROTO_IS_LAST */
		fc_p.proto.len = PIPE_MAX_PAYLOAD;
		memcpy(fc_p.proto.data, fc_data, PIPE_MAX_PAYLOAD);
		fc_rc = write(fc_fd, &fc_p, PIPE_HEADER_SIZE + PIPE_MAX_PAYLOAD);
		(void) fc_rc;
		fc_data += PIPE_MAX_PAYLOAD;
		fc_len -= PIPE_MAX_PAYLOAD;
	}

	/* 写入最后一块 */
	fc_p.proto.flags |= PIPE_PROTO_IS_LAST;
	fc_p.proto.len = fc_len;
	memcpy(fc_p.proto.data, fc_data, fc_len);
	fc_rc = write(fc_fd, &fc_p, PIPE_HEADER_SIZE + fc_len);
	(void) fc_rc;
}

#ifdef FDD
FDB_TYPE_WRITE_AUDIT_HOOK fdb_write_audit_hook = NULL;
void fdb_write_audit_log(char *fc_data, int fc_len)
{
	if (MyBackendType == B_LOGGER)
	{
		write_syslogger_file(fc_data, fc_len, LOG_DESTINATION_AUDIT);
	}
	else
	{
		write_pipe_chunks(fc_data, fc_len, LOG_DESTINATION_AUDIT);//写入审计日志
	}
}
#endif //FDD

/*
 * 将文本字符串附加到为客户端构建的错误报告中。
 *
 * 这通常与 pq_sendstring() 相同，但如果我们在错误递归问题中，我们会跳过编码转换，因为可能的问题出在编码转换子系统本身。其他代码应确保传入的字符串将是普通的 7 位 ASCII，因此在这种情况下无需转换。（特别是，我们禁用了错误消息的本地化，以帮助确保这是正确的。）
 */
static void fc_err_sendstring(StringInfo fc_buf, const char *fc_str)
{
	if (in_error_recursion_trouble())
		pq_send_ascii_string(fc_buf, fc_str);
	else
		pq_sendstring(fc_buf, fc_str);
}

/*
 * 将错误报告写入客户端
 */
static void fc_send_message_to_frontend(ErrorData *fc_edata)
{
	StringInfoData fc_msgbuf;

	/*
	 * 我们不再支持 3.0 之前的 FE/BE 协议，除了这里。如果客户端尝试使用较旧的协议版本连接，发送“协议版本不受支持”的错误以客户端能够理解的格式是不错的。如果协议尚未设置，在后端启动的早期阶段，假设使用现代协议。
	 */
	if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 3 || FrontendProtocol == 0)
	{
		/* 新样式带有单独的字段 */
		const char *fc_sev;
		char		fc_tbuf[12];

		/* 'N'（通知）表示非致命条件，'E'表示错误 */
		pq_beginmessage(&fc_msgbuf, (fc_edata->elevel < ERROR) ? 'N' : 'E');

		fc_sev = error_severity(fc_edata->elevel);
		pq_sendbyte(&fc_msgbuf, PG_DIAG_SEVERITY);
		fc_err_sendstring(&fc_msgbuf, _(fc_sev));
		pq_sendbyte(&fc_msgbuf, PG_DIAG_SEVERITY_NONLOCALIZED);
		fc_err_sendstring(&fc_msgbuf, fc_sev);

		pq_sendbyte(&fc_msgbuf, PG_DIAG_SQLSTATE);
		fc_err_sendstring(&fc_msgbuf, unpack_sql_state(fc_edata->sqlerrcode));

		/* M 字段是协议要求的，因此始终发送一些内容 */
		pq_sendbyte(&fc_msgbuf, PG_DIAG_MESSAGE_PRIMARY);
		if (fc_edata->message)
			fc_err_sendstring(&fc_msgbuf, fc_edata->message);
		else
			fc_err_sendstring(&fc_msgbuf, _("missing error text"));

		if (fc_edata->detail)
		{
			pq_sendbyte(&fc_msgbuf, PG_DIAG_MESSAGE_DETAIL);
			fc_err_sendstring(&fc_msgbuf, fc_edata->detail);
		}

		/* detail_log 有意在此不使用 */

		if (fc_edata->hint)
		{
			pq_sendbyte(&fc_msgbuf, PG_DIAG_MESSAGE_HINT);
			fc_err_sendstring(&fc_msgbuf, fc_edata->hint);
		}

		if (fc_edata->context)
		{
			pq_sendbyte(&fc_msgbuf, PG_DIAG_CONTEXT);
			fc_err_sendstring(&fc_msgbuf, fc_edata->context);
		}

		if (fc_edata->schema_name)
		{
			pq_sendbyte(&fc_msgbuf, PG_DIAG_SCHEMA_NAME);
			fc_err_sendstring(&fc_msgbuf, fc_edata->schema_name);
		}

		if (fc_edata->table_name)
		{
			pq_sendbyte(&fc_msgbuf, PG_DIAG_TABLE_NAME);
			fc_err_sendstring(&fc_msgbuf, fc_edata->table_name);
		}

		if (fc_edata->column_name)
		{
			pq_sendbyte(&fc_msgbuf, PG_DIAG_COLUMN_NAME);
			fc_err_sendstring(&fc_msgbuf, fc_edata->column_name);
		}

		if (fc_edata->datatype_name)
		{
			pq_sendbyte(&fc_msgbuf, PG_DIAG_DATATYPE_NAME);
			fc_err_sendstring(&fc_msgbuf, fc_edata->datatype_name);
		}

		if (fc_edata->constraint_name)
		{
			pq_sendbyte(&fc_msgbuf, PG_DIAG_CONSTRAINT_NAME);
			fc_err_sendstring(&fc_msgbuf, fc_edata->constraint_name);
		}

		if (fc_edata->cursorpos > 0)
		{
			snprintf(fc_tbuf, sizeof(fc_tbuf), "%d", fc_edata->cursorpos);
			pq_sendbyte(&fc_msgbuf, PG_DIAG_STATEMENT_POSITION);
			fc_err_sendstring(&fc_msgbuf, fc_tbuf);
		}

		if (fc_edata->internalpos > 0)
		{
			snprintf(fc_tbuf, sizeof(fc_tbuf), "%d", fc_edata->internalpos);
			pq_sendbyte(&fc_msgbuf, PG_DIAG_INTERNAL_POSITION);
			fc_err_sendstring(&fc_msgbuf, fc_tbuf);
		}

		if (fc_edata->internalquery)
		{
			pq_sendbyte(&fc_msgbuf, PG_DIAG_INTERNAL_QUERY);
			fc_err_sendstring(&fc_msgbuf, fc_edata->internalquery);
		}

		if (fc_edata->filename)
		{
			pq_sendbyte(&fc_msgbuf, PG_DIAG_SOURCE_FILE);
			fc_err_sendstring(&fc_msgbuf, fc_edata->filename);
		}

		if (fc_edata->lineno > 0)
		{
			snprintf(fc_tbuf, sizeof(fc_tbuf), "%d", fc_edata->lineno);
			pq_sendbyte(&fc_msgbuf, PG_DIAG_SOURCE_LINE);
			fc_err_sendstring(&fc_msgbuf, fc_tbuf);
		}

		if (fc_edata->funcname)
		{
			pq_sendbyte(&fc_msgbuf, PG_DIAG_SOURCE_FUNCTION);
			fc_err_sendstring(&fc_msgbuf, fc_edata->funcname);
		}

		pq_sendbyte(&fc_msgbuf, '\0'); /* 终止符 */

		pq_endmessage(&fc_msgbuf);
	}
	else
	{
		/* 旧样式 --- 制作向后兼容的消息 */
		StringInfoData fc_buf;

		initStringInfo(&fc_buf);

		appendStringInfo(&fc_buf, "%s:  ", _(error_severity(fc_edata->elevel)));

		if (fc_edata->message)
			appendStringInfoString(&fc_buf, fc_edata->message);
		else
			appendStringInfoString(&fc_buf, _("missing error text"));

		appendStringInfoChar(&fc_buf, '\n');

		/* 'N'（通知）表示非致命条件，'E'表示错误 */
		pq_putmessage_v2((fc_edata->elevel < ERROR) ? 'N' : 'E', fc_buf.data, fc_buf.len + 1);

		pfree(fc_buf.data);
	}

	/*
	 * 这个刷新通常不是必需的，因为 postgres.c 会在控制返回到主循环时刷新等待数据。但是在这里保留它似乎是最好的，这样如果后端在返回主循环之前崩溃，客户端就会知道发生了什么……错误/通知消息本就不应是性能关键路径，因此额外的刷新不会有太大影响……
	 */
	pq_flush();
}


/*
 * 支持格式化错误信息的例程。
 */


/*
 * error_severity --- 获取表示elevel的字符串
 *
 * 这个字符串在这里没有本地化，但我们标记这些字符串以便翻译
 * 这样调用者可以在结果上调用_()。
 */
const char * error_severity(int fc_elevel)
{
	const char *fc_prefix;

	switch (fc_elevel)
	{
		case DEBUG1:
		case DEBUG2:
		case DEBUG3:
		case DEBUG4:
		case DEBUG5:
			fc_prefix = gettext_noop("DEBUG");
			break;
		case LOG:
		case LOG_SERVER_ONLY:
			fc_prefix = gettext_noop("LOG");
			break;
		case INFO:
			fc_prefix = gettext_noop("INFO");
			break;
		case NOTICE:
			fc_prefix = gettext_noop("NOTICE");
			break;
		case WARNING:
		case WARNING_CLIENT_ONLY:
			fc_prefix = gettext_noop("WARNING");
			break;
		case ERROR:
			fc_prefix = gettext_noop("ERROR");
			break;
		case FATAL:
			fc_prefix = gettext_noop("FATAL");
			break;
		case PANIC:
			fc_prefix = gettext_noop("PANIC");
			break;
		default:
			fc_prefix = "???";
			break;
	}

	return fc_prefix;
}


/*
 *	append_with_tabs
 *
 *	将字符串附加到StringInfo缓冲区，在任何换行后插入一个制表符。
 */
static void fc_append_with_tabs(StringInfo fc_buf, const char *fc_str)
{
	char		fc_ch;

	while ((fc_ch = *fc_str++) != '\0')
	{
		appendStringInfoCharMacro(fc_buf, fc_ch);
		if (fc_ch == '\n')
			appendStringInfoCharMacro(fc_buf, '\t');
	}
}


/*
 * 将错误写入stderr（或在stderr不可用时以相同方式）。在使用
 * ereport/elog之前，必须安全地使用（内存上下文、GUC加载等）。
 */
void write_stderr(const char *fc_fmt,...)
{
	va_list		fc_ap;

#ifdef WIN32
	char		errbuf[2048];	/* 任意大小？ */
#endif

	fc_fmt = _(fc_fmt);

	va_start(fc_ap, fc_fmt);
#ifndef WIN32
	/* 在 Unix 上，我们只是将信息输出到 stderr */
	vfprintf(stderr, fc_fmt, fc_ap);
	fflush(stderr);
#else
	vsnprintf(errbuf, sizeof(errbuf), fmt, ap);

	/*
	 * 在 Win32 上，如果在控制台运行则输出到 stderr，
	 * 如果作为服务运行则写入事件日志
	 */
	if (pgwin32_is_service())	/* 作为服务运行 */
	{
		write_eventlog(ERROR, errbuf, strlen(errbuf));
	}
	else
	{
		/* 不作为服务运行，写入 stderr */
		write_console(errbuf, strlen(errbuf));
		fflush(stderr);
	}
#endif
	va_end(fc_ap);
}


/*
 * 使用仅异步信号安全的函数将消息写入STDERR。可以
 * 用于安全地从信号处理程序发出消息。
 *
 * TODO: 可能可以安全地进行有限的字符串
 * 插值（例如，%s和%d），但目前不支持。
 */
void write_stderr_signal_safe(const char *fc_str)
{
	int			fc_nwritten = 0;
	int			fc_ntotal = strlen(fc_str);

	while (fc_nwritten < fc_ntotal)
	{
		int			fc_rc;

		fc_rc = write(STDERR_FILENO, fc_str + fc_nwritten, fc_ntotal - fc_nwritten);

		/* 对错误只是一味放弃。我们没有其他可以做的。 */
		if (fc_rc == -1)
			return;

		fc_nwritten += fc_rc;
	}
}


/*
 * 根据trace_recovery_messages调整恢复相关消息的级别。
 *
 * 参数是消息的默认日志级别，例如DEBUG2。（这
 * 只应用于DEBUGn日志消息，否则它无效。）
 * 如果级别>= trace_recovery_messages，我们返回LOG，导致
 * 消息无条件记录（对于大多数设置的
 * log_min_messages）。否则，我们返回未改变的参数。
 * 消息将根据log_min_messages的设置进行显示。
 *
 * 意图是保持此状态至少在9.0生产版本的整个期间，
 * 以便我们更容易诊断现场的生产问题。
 * 然而，它最终应该消失，因为它是一个难看的且
 * 难以解释的权宜之计。
 */
int trace_recovery(int fc_trace_level)
{
	if (fc_trace_level < LOG &&
		fc_trace_level >= trace_recovery_messages)
		return LOG;

	return fc_trace_level;
}
